blob: b1bf4dffa92fecab2ecc73064af9c136ae477c82 [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#
Benjamin Peterson1105f342011-06-27 09:14:34 -05005# Written by James Roskind
Guido van Rossumadb31051994-06-23 11:42:52 +00006# Based on prior profile module by Sjoerd Mullender...
7# which was hacked somewhat by: Guido van Rossum
Guido van Rossumadb31051994-06-23 11:42:52 +00008
Benjamin Peterson1105f342011-06-27 09:14:34 -05009# Copyright Disney Enterprises, Inc. All Rights Reserved.
10# Licensed to PSF under a Contributor Agreement
Tim Peters2344fae2001-01-15 00:50:52 +000011#
Benjamin Peterson1105f342011-06-27 09:14:34 -050012# Licensed under the Apache License, Version 2.0 (the "License");
13# you may not use this file except in compliance with the License.
14# You may obtain a copy of the License at
Tim Peters2344fae2001-01-15 00:50:52 +000015#
Benjamin Peterson1105f342011-06-27 09:14:34 -050016# http://www.apache.org/licenses/LICENSE-2.0
17#
18# Unless required by applicable law or agreed to in writing, software
19# distributed under the License is distributed on an "AS IS" BASIS,
20# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
21# either express or implied. See the License for the specific language
22# governing permissions and limitations under the License.
Guido van Rossumadb31051994-06-23 11:42:52 +000023
Skip Montanaro262fb922006-04-21 02:31:07 +000024import sys
Guido van Rossumadb31051994-06-23 11:42:52 +000025import os
26import time
Guido van Rossumadb31051994-06-23 11:42:52 +000027import marshal
Guido van Rossum9694fca1997-10-22 21:00:49 +000028import re
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
Skip Montanaro262fb922006-04-21 02:31:07 +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]
Tim Peters7d016852001-10-08 06:13:19 +0000141 if type(self) != type(other) or self.__class__ != other.__class__:
Tim Peters2344fae2001-01-15 00:50:52 +0000142 other = Stats(other)
Tim Peters7d016852001-10-08 06:13:19 +0000143 self.files += other.files
144 self.total_calls += other.total_calls
145 self.prim_calls += other.prim_calls
146 self.total_tt += other.total_tt
Raymond Hettingere0d49722002-06-02 18:55:56 +0000147 for func in other.top_level:
Tim Peters2344fae2001-01-15 00:50:52 +0000148 self.top_level[func] = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000149
Tim Peters2344fae2001-01-15 00:50:52 +0000150 if self.max_name_len < other.max_name_len:
151 self.max_name_len = other.max_name_len
Guido van Rossumadb31051994-06-23 11:42:52 +0000152
Tim Peters2344fae2001-01-15 00:50:52 +0000153 self.fcn_list = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000154
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
209 if len(field) == 1 and type(field[0]) == type(1):
210 # Be compatible with old profiler
Tim Peters7d016852001-10-08 06:13:19 +0000211 field = [ {-1: "stdname",
212 0:"calls",
213 1:"time",
Tim Peters2344fae2001-01-15 00:50:52 +0000214 2: "cumulative" } [ field[0] ] ]
215
216 sort_arg_defs = self.get_sort_arg_defs()
217 sort_tuple = ()
218 self.sort_type = ""
219 connector = ""
220 for word in field:
221 sort_tuple = sort_tuple + sort_arg_defs[word][0]
Tim Peters7d016852001-10-08 06:13:19 +0000222 self.sort_type += connector + sort_arg_defs[word][1]
Tim Peters2344fae2001-01-15 00:50:52 +0000223 connector = ", "
224
225 stats_list = []
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
Brett Cannonc3ce0e52008-08-03 22:52:42 +0000230 stats_list.sort(key=CmpToKey(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
291 if type(sel) == type(""):
292 new_list = []
293 for func in list:
294 if re.search(sel, func_std_string(func)):
295 new_list.append(func)
296 else:
297 count = len(list)
298 if type(sel) == type(1.0) and 0.0 <= sel < 1.0:
Tim Peters7d016852001-10-08 06:13:19 +0000299 count = int(count * sel + .5)
Tim Peters2344fae2001-01-15 00:50:52 +0000300 new_list = list[:count]
301 elif type(sel) == type(1) and 0 <= sel < count:
302 count = sel
303 new_list = list[:count]
304 if len(list) != len(new_list):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000305 msg = msg + " List reduced from %r to %r due to restriction <%r>\n" % (
306 len(list), len(new_list), sel)
Guido van Rossumadb31051994-06-23 11:42:52 +0000307
Tim Peters2344fae2001-01-15 00:50:52 +0000308 return new_list, msg
Guido van Rossumadb31051994-06-23 11:42:52 +0000309
Tim Peters2344fae2001-01-15 00:50:52 +0000310 def get_print_list(self, sel_list):
311 width = self.max_name_len
312 if self.fcn_list:
313 list = self.fcn_list[:]
314 msg = " Ordered by: " + self.sort_type + '\n'
315 else:
316 list = self.stats.keys()
317 msg = " Random listing order was used\n"
318
319 for selection in sel_list:
Tim Peters7d016852001-10-08 06:13:19 +0000320 list, msg = self.eval_print_amount(selection, list, msg)
Tim Peters2344fae2001-01-15 00:50:52 +0000321
322 count = len(list)
323
324 if not list:
325 return 0, list
Skip Montanaro262fb922006-04-21 02:31:07 +0000326 print >> self.stream, msg
Tim Peters2344fae2001-01-15 00:50:52 +0000327 if count < len(self.stats):
328 width = 0
329 for func in list:
330 if len(func_std_string(func)) > width:
331 width = len(func_std_string(func))
332 return width+2, list
333
334 def print_stats(self, *amount):
335 for filename in self.files:
Skip Montanaro262fb922006-04-21 02:31:07 +0000336 print >> self.stream, filename
337 if self.files: print >> self.stream
Tim Peters7d016852001-10-08 06:13:19 +0000338 indent = ' ' * 8
Raymond Hettingere0d49722002-06-02 18:55:56 +0000339 for func in self.top_level:
Skip Montanaro262fb922006-04-21 02:31:07 +0000340 print >> self.stream, indent, func_get_function_name(func)
Tim Peters2344fae2001-01-15 00:50:52 +0000341
Skip Montanaro262fb922006-04-21 02:31:07 +0000342 print >> self.stream, indent, self.total_calls, "function calls",
Tim Peters2344fae2001-01-15 00:50:52 +0000343 if self.total_calls != self.prim_calls:
Skip Montanaro262fb922006-04-21 02:31:07 +0000344 print >> self.stream, "(%d primitive calls)" % self.prim_calls,
345 print >> self.stream, "in %.3f CPU seconds" % self.total_tt
346 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000347 width, list = self.get_print_list(amount)
348 if list:
349 self.print_title()
350 for func in list:
351 self.print_line(func)
Skip Montanaro262fb922006-04-21 02:31:07 +0000352 print >> self.stream
353 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000354 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000355
Tim Peters2344fae2001-01-15 00:50:52 +0000356 def print_callees(self, *amount):
357 width, list = self.get_print_list(amount)
358 if list:
359 self.calc_callees()
360
361 self.print_call_heading(width, "called...")
362 for func in list:
Raymond Hettinger54f02222002-06-01 14:18:47 +0000363 if func in self.all_callees:
Tim Peters7d016852001-10-08 06:13:19 +0000364 self.print_call_line(width, func, self.all_callees[func])
Tim Peters2344fae2001-01-15 00:50:52 +0000365 else:
366 self.print_call_line(width, func, {})
Skip Montanaro262fb922006-04-21 02:31:07 +0000367 print >> self.stream
368 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000369 return self
370
371 def print_callers(self, *amount):
372 width, list = self.get_print_list(amount)
373 if list:
374 self.print_call_heading(width, "was called by...")
375 for func in list:
376 cc, nc, tt, ct, callers = self.stats[func]
Armin Rigoa871ef22006-02-08 12:53:56 +0000377 self.print_call_line(width, func, callers, "<-")
Skip Montanaro262fb922006-04-21 02:31:07 +0000378 print >> self.stream
379 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000380 return self
381
382 def print_call_heading(self, name_size, column_title):
Skip Montanaro262fb922006-04-21 02:31:07 +0000383 print >> self.stream, "Function ".ljust(name_size) + column_title
Armin Rigoa871ef22006-02-08 12:53:56 +0000384 # print sub-header only if we have new-style callers
385 subheader = False
386 for cc, nc, tt, ct, callers in self.stats.itervalues():
387 if callers:
388 value = callers.itervalues().next()
389 subheader = isinstance(value, tuple)
390 break
391 if subheader:
Skip Montanaro262fb922006-04-21 02:31:07 +0000392 print >> self.stream, " "*name_size + " ncalls tottime cumtime"
Guido van Rossumadb31051994-06-23 11:42:52 +0000393
Armin Rigoa871ef22006-02-08 12:53:56 +0000394 def print_call_line(self, name_size, source, call_dict, arrow="->"):
Skip Montanaro262fb922006-04-21 02:31:07 +0000395 print >> self.stream, func_std_string(source).ljust(name_size) + arrow,
Tim Peters2344fae2001-01-15 00:50:52 +0000396 if not call_dict:
Skip Montanaro262fb922006-04-21 02:31:07 +0000397 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000398 return
399 clist = call_dict.keys()
400 clist.sort()
Tim Peters2344fae2001-01-15 00:50:52 +0000401 indent = ""
402 for func in clist:
403 name = func_std_string(func)
Armin Rigoa871ef22006-02-08 12:53:56 +0000404 value = call_dict[func]
405 if isinstance(value, tuple):
406 nc, cc, tt, ct = value
407 if nc != cc:
408 substats = '%d/%d' % (nc, cc)
409 else:
410 substats = '%d' % (nc,)
411 substats = '%s %s %s %s' % (substats.rjust(7+2*len(indent)),
412 f8(tt), f8(ct), name)
413 left_width = name_size + 1
414 else:
415 substats = '%s(%r) %s' % (name, value, f8(self.stats[func][3]))
416 left_width = name_size + 3
Skip Montanaro262fb922006-04-21 02:31:07 +0000417 print >> self.stream, indent*left_width + substats
Tim Peters2344fae2001-01-15 00:50:52 +0000418 indent = " "
419
Tim Peters2344fae2001-01-15 00:50:52 +0000420 def print_title(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000421 print >> self.stream, ' ncalls tottime percall cumtime percall',
422 print >> self.stream, 'filename:lineno(function)'
Tim Peters2344fae2001-01-15 00:50:52 +0000423
424 def print_line(self, func): # hack : should print percentages
425 cc, nc, tt, ct, callers = self.stats[func]
Tim Peters7d016852001-10-08 06:13:19 +0000426 c = str(nc)
Tim Peters2344fae2001-01-15 00:50:52 +0000427 if nc != cc:
Tim Peters7d016852001-10-08 06:13:19 +0000428 c = c + '/' + str(cc)
Skip Montanaro262fb922006-04-21 02:31:07 +0000429 print >> self.stream, c.rjust(9),
430 print >> self.stream, f8(tt),
Tim Peters2344fae2001-01-15 00:50:52 +0000431 if nc == 0:
Skip Montanaro262fb922006-04-21 02:31:07 +0000432 print >> self.stream, ' '*8,
Tim Peters2344fae2001-01-15 00:50:52 +0000433 else:
Ezio Melotti8dc04a42010-08-02 00:24:26 +0000434 print >> self.stream, f8(float(tt)/nc),
Skip Montanaro262fb922006-04-21 02:31:07 +0000435 print >> self.stream, f8(ct),
Tim Peters2344fae2001-01-15 00:50:52 +0000436 if cc == 0:
Skip Montanaro262fb922006-04-21 02:31:07 +0000437 print >> self.stream, ' '*8,
Tim Peters2344fae2001-01-15 00:50:52 +0000438 else:
Ezio Melotti8dc04a42010-08-02 00:24:26 +0000439 print >> self.stream, f8(float(ct)/cc),
Skip Montanaro262fb922006-04-21 02:31:07 +0000440 print >> self.stream, func_std_string(func)
Tim Peters2344fae2001-01-15 00:50:52 +0000441
Guido van Rossumadb31051994-06-23 11:42:52 +0000442class TupleComp:
Tim Peters2344fae2001-01-15 00:50:52 +0000443 """This class provides a generic function for comparing any two tuples.
444 Each instance records a list of tuple-indices (from most significant
445 to least significant), and sort direction (ascending or decending) for
446 each tuple-index. The compare functions can then be used as the function
447 argument to the system sort() function when a list of tuples need to be
448 sorted in the instances order."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000449
Tim Peters2344fae2001-01-15 00:50:52 +0000450 def __init__(self, comp_select_list):
451 self.comp_select_list = comp_select_list
Guido van Rossumadb31051994-06-23 11:42:52 +0000452
Tim Peters2344fae2001-01-15 00:50:52 +0000453 def compare (self, left, right):
454 for index, direction in self.comp_select_list:
455 l = left[index]
456 r = right[index]
457 if l < r:
458 return -direction
459 if l > r:
460 return direction
461 return 0
Guido van Rossumadb31051994-06-23 11:42:52 +0000462
Brett Cannonc3ce0e52008-08-03 22:52:42 +0000463def CmpToKey(mycmp):
464 """Convert a cmp= function into a key= function"""
465 class K(object):
466 def __init__(self, obj):
467 self.obj = obj
468 def __lt__(self, other):
469 return mycmp(self.obj, other.obj) == -1
470 return K
471
472
Guido van Rossumadb31051994-06-23 11:42:52 +0000473#**************************************************************************
Tim Peters7d016852001-10-08 06:13:19 +0000474# func_name is a triple (file:string, line:int, name:string)
Guido van Rossumadb31051994-06-23 11:42:52 +0000475
476def func_strip_path(func_name):
Fred Drake9c439102003-05-14 14:28:09 +0000477 filename, line, name = func_name
478 return os.path.basename(filename), line, name
Guido van Rossumadb31051994-06-23 11:42:52 +0000479
480def func_get_function_name(func):
Tim Peters2344fae2001-01-15 00:50:52 +0000481 return func[2]
Guido van Rossumadb31051994-06-23 11:42:52 +0000482
483def func_std_string(func_name): # match what old profile produced
Armin Rigoa871ef22006-02-08 12:53:56 +0000484 if func_name[:2] == ('~', 0):
485 # special case for built-in functions
486 name = func_name[2]
487 if name.startswith('<') and name.endswith('>'):
488 return '{%s}' % name[1:-1]
489 else:
490 return name
491 else:
492 return "%s:%d(%s)" % func_name
Guido van Rossumadb31051994-06-23 11:42:52 +0000493
494#**************************************************************************
495# The following functions combine statists for pairs functions.
496# The bulk of the processing involves correctly handling "call" lists,
Tim Peters2344fae2001-01-15 00:50:52 +0000497# such as callers and callees.
Guido van Rossumadb31051994-06-23 11:42:52 +0000498#**************************************************************************
499
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000500def add_func_stats(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000501 """Add together all the stats for two profile entries."""
502 cc, nc, tt, ct, callers = source
503 t_cc, t_nc, t_tt, t_ct, t_callers = target
Tim Peters7d016852001-10-08 06:13:19 +0000504 return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct,
Tim Peters2344fae2001-01-15 00:50:52 +0000505 add_callers(t_callers, callers))
Guido van Rossumadb31051994-06-23 11:42:52 +0000506
Guido van Rossumadb31051994-06-23 11:42:52 +0000507def add_callers(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000508 """Combine two caller lists in a single list."""
509 new_callers = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000510 for func, caller in target.iteritems():
511 new_callers[func] = caller
512 for func, caller in source.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000513 if func in new_callers:
Georg Brandl800a3542010-08-02 17:40:28 +0000514 if isinstance(caller, tuple):
515 # format used by cProfile
516 new_callers[func] = tuple([i[0] + i[1] for i in
517 zip(caller, new_callers[func])])
518 else:
519 # format used by profile
520 new_callers[func] += caller
Tim Peters2344fae2001-01-15 00:50:52 +0000521 else:
Raymond Hettingere0d49722002-06-02 18:55:56 +0000522 new_callers[func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000523 return new_callers
Guido van Rossumadb31051994-06-23 11:42:52 +0000524
Guido van Rossumadb31051994-06-23 11:42:52 +0000525def count_calls(callers):
Tim Peters2344fae2001-01-15 00:50:52 +0000526 """Sum the caller statistics to get total number of calls received."""
527 nc = 0
Raymond Hettingere0d49722002-06-02 18:55:56 +0000528 for calls in callers.itervalues():
529 nc += calls
Tim Peters2344fae2001-01-15 00:50:52 +0000530 return nc
Guido van Rossumadb31051994-06-23 11:42:52 +0000531
532#**************************************************************************
533# The following functions support printing of reports
534#**************************************************************************
535
536def f8(x):
Tim Peters7d016852001-10-08 06:13:19 +0000537 return "%8.3f" % x
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000538
539#**************************************************************************
540# Statistics browser added by ESR, April 2001
541#**************************************************************************
542
543if __name__ == '__main__':
544 import cmd
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000545 try:
546 import readline
Fred Drakee8187612001-05-11 19:21:41 +0000547 except ImportError:
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000548 pass
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000549
550 class ProfileBrowser(cmd.Cmd):
551 def __init__(self, profile=None):
Martin v. Löwis66b6e192001-07-28 14:44:03 +0000552 cmd.Cmd.__init__(self)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000553 self.prompt = "% "
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000554 if profile is not None:
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000555 self.stats = Stats(profile)
Neal Norwitze588c2b2006-06-11 07:27:56 +0000556 self.stream = self.stats.stream
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000557 else:
558 self.stats = None
Neal Norwitze588c2b2006-06-11 07:27:56 +0000559 self.stream = sys.stdout
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000560
561 def generic(self, fn, line):
562 args = line.split()
563 processed = []
564 for term in args:
565 try:
566 processed.append(int(term))
567 continue
568 except ValueError:
569 pass
570 try:
571 frac = float(term)
572 if frac > 1 or frac < 0:
Skip Montanaro262fb922006-04-21 02:31:07 +0000573 print >> self.stream, "Fraction argument must be in [0, 1]"
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000574 continue
575 processed.append(frac)
576 continue
577 except ValueError:
578 pass
579 processed.append(term)
580 if self.stats:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000581 getattr(self.stats, fn)(*processed)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000582 else:
Skip Montanaro262fb922006-04-21 02:31:07 +0000583 print >> self.stream, "No statistics object is loaded."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000584 return 0
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000585 def generic_help(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000586 print >> self.stream, "Arguments may be:"
587 print >> self.stream, "* An integer maximum number of entries to print."
588 print >> self.stream, "* A decimal fractional number between 0 and 1, controlling"
589 print >> self.stream, " what fraction of selected entries to print."
590 print >> self.stream, "* A regular expression; only entries with function names"
591 print >> self.stream, " that match it are printed."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000592
593 def do_add(self, line):
Georg Brandld9ede202010-08-01 22:13:33 +0000594 if self.stats:
595 self.stats.add(line)
596 else:
597 print >> self.stream, "No statistics object is loaded."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000598 return 0
599 def help_add(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000600 print >> self.stream, "Add profile info from given file to current statistics object."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000601
602 def do_callees(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000603 return self.generic('print_callees', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000604 def help_callees(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000605 print >> self.stream, "Print callees statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000606 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000607
608 def do_callers(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000609 return self.generic('print_callers', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000610 def help_callers(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000611 print >> self.stream, "Print callers statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000612 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000613
614 def do_EOF(self, line):
Skip Montanaro262fb922006-04-21 02:31:07 +0000615 print >> self.stream, ""
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000616 return 1
617 def help_EOF(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000618 print >> self.stream, "Leave the profile brower."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000619
620 def do_quit(self, line):
621 return 1
622 def help_quit(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_read(self, line):
626 if line:
627 try:
628 self.stats = Stats(line)
629 except IOError, args:
Skip Montanaro262fb922006-04-21 02:31:07 +0000630 print >> self.stream, args[1]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000631 return
Georg Brandld9ede202010-08-01 22:13:33 +0000632 except Exception as err:
633 print >> self.stream, err.__class__.__name__ + ':', err
634 return
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000635 self.prompt = line + "% "
Martin v. Löwis22adac52001-06-07 05:49:05 +0000636 elif len(self.prompt) > 2:
Georg Brandld9ede202010-08-01 22:13:33 +0000637 line = self.prompt[:-2]
638 self.do_read(line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000639 else:
Skip Montanaro262fb922006-04-21 02:31:07 +0000640 print >> self.stream, "No statistics object is current -- cannot reload."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000641 return 0
642 def help_read(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000643 print >> self.stream, "Read in profile data from a specified file."
Georg Brandld9ede202010-08-01 22:13:33 +0000644 print >> self.stream, "Without argument, reload the current file."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000645
646 def do_reverse(self, line):
Georg Brandld9ede202010-08-01 22:13:33 +0000647 if self.stats:
648 self.stats.reverse_order()
649 else:
650 print >> self.stream, "No statistics object is loaded."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000651 return 0
652 def help_reverse(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000653 print >> self.stream, "Reverse the sort order of the profiling report."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000654
655 def do_sort(self, line):
Georg Brandld9ede202010-08-01 22:13:33 +0000656 if not self.stats:
657 print >> self.stream, "No statistics object is loaded."
658 return
Raymond Hettingere0d49722002-06-02 18:55:56 +0000659 abbrevs = self.stats.get_sort_arg_defs()
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000660 if line and not filter(lambda x,a=abbrevs: x not in a,line.split()):
Guido van Rossum68468eb2003-02-27 20:14:51 +0000661 self.stats.sort_stats(*line.split())
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000662 else:
Skip Montanaro262fb922006-04-21 02:31:07 +0000663 print >> self.stream, "Valid sort keys (unique prefixes are accepted):"
Raymond Hettingere0d49722002-06-02 18:55:56 +0000664 for (key, value) in Stats.sort_arg_dict_default.iteritems():
Skip Montanaro262fb922006-04-21 02:31:07 +0000665 print >> self.stream, "%s -- %s" % (key, value[1])
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000666 return 0
667 def help_sort(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000668 print >> self.stream, "Sort profile data according to specified keys."
669 print >> self.stream, "(Typing `sort' without arguments lists valid keys.)"
Martin v. Löwis27c430e2001-07-30 10:21:13 +0000670 def complete_sort(self, text, *args):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000671 return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000672
673 def do_stats(self, line):
674 return self.generic('print_stats', line)
675 def help_stats(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000676 print >> self.stream, "Print statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000677 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000678
679 def do_strip(self, line):
Georg Brandld9ede202010-08-01 22:13:33 +0000680 if self.stats:
681 self.stats.strip_dirs()
682 else:
683 print >> self.stream, "No statistics object is loaded."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000684 def help_strip(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000685 print >> self.stream, "Strip leading path information from filenames in the report."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000686
Georg Brandld9ede202010-08-01 22:13:33 +0000687 def help_help(self):
688 print >> self.stream, "Show help for a given command."
689
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000690 def postcmd(self, stop, line):
691 if stop:
692 return stop
693 return None
694
695 import sys
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000696 if len(sys.argv) > 1:
697 initprofile = sys.argv[1]
698 else:
699 initprofile = None
700 try:
Neal Norwitze588c2b2006-06-11 07:27:56 +0000701 browser = ProfileBrowser(initprofile)
702 print >> browser.stream, "Welcome to the profile statistics browser."
703 browser.cmdloop()
704 print >> browser.stream, "Goodbye."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000705 except KeyboardInterrupt:
706 pass
707
708# That's all, folks.