blob: 91cdee186b0d8a947fc0ab5be027816aca1536a2 [file] [log] [blame]
Guido van Rossum54f22ed2000-02-04 15:10:34 +00001"""Class for printing reports on profiled python code."""
2
Benjamin Peterson1105f342011-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 Peterson1105f342011-06-27 09:14:34 -05007# Copyright Disney Enterprises, Inc. All Rights Reserved.
8# Licensed to PSF under a Contributor Agreement
Tim Peters2344fae2001-01-15 00:50:52 +00009#
Benjamin Peterson1105f342011-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
Tim Peters2344fae2001-01-15 00:50:52 +000013#
Benjamin Peterson1105f342011-06-27 09:14:34 -050014# http://www.apache.org/licenses/LICENSE-2.0
15#
16# 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
Skip Montanaro262fb922006-04-21 02:31:07 +000022import sys
Guido van Rossumadb31051994-06-23 11:42:52 +000023import os
24import time
Guido van Rossumadb31051994-06-23 11:42:52 +000025import marshal
Guido van Rossum9694fca1997-10-22 21:00:49 +000026import re
Guido van Rossumadb31051994-06-23 11:42:52 +000027
Skip Montanaroc62c81e2001-02-12 02:00:42 +000028__all__ = ["Stats"]
29
Guido van Rossumadb31051994-06-23 11:42:52 +000030class Stats:
Tim Peters2344fae2001-01-15 00:50:52 +000031 """This class is used for creating reports from data generated by the
32 Profile class. It is a "friend" of that class, and imports data either
33 by direct access to members of Profile class, or by reading in a dictionary
34 that was emitted (via marshal) from the Profile class.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000035
Tim Peters2344fae2001-01-15 00:50:52 +000036 The big change from the previous Profiler (in terms of raw functionality)
37 is that an "add()" method has been provided to combine Stats from
38 several distinct profile runs. Both the constructor and the add()
39 method now take arbitrarily many file names as arguments.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000040
Tim Peters2344fae2001-01-15 00:50:52 +000041 All the print methods now take an argument that indicates how many lines
42 to print. If the arg is a floating point number between 0 and 1.0, then
43 it is taken as a decimal percentage of the available lines to be printed
44 (e.g., .1 means print 10% of all available lines). If it is an integer,
45 it is taken to mean the number of lines of data that you wish to have
46 printed.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000047
Tim Peters2344fae2001-01-15 00:50:52 +000048 The sort_stats() method now processes some additional options (i.e., in
Skip Montanaro262fb922006-04-21 02:31:07 +000049 addition to the old -1, 0, 1, or 2). It takes an arbitrary number of
50 quoted strings to select the sort order. For example sort_stats('time',
51 'name') sorts on the major key of 'internal function time', and on the
52 minor key of 'the name of the function'. Look at the two tables in
53 sort_stats() and get_sort_arg_defs(self) for more examples.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000054
Skip Montanaro262fb922006-04-21 02:31:07 +000055 All methods return self, so you can string together commands like:
Tim Peters2344fae2001-01-15 00:50:52 +000056 Stats('foo', 'goo').strip_dirs().sort_stats('calls').\
57 print_stats(5).print_callers(5)
58 """
59
Skip Montanaro262fb922006-04-21 02:31:07 +000060 def __init__(self, *args, **kwds):
61 # I can't figure out how to explictly specify a stream keyword arg
62 # with *args:
63 # def __init__(self, *args, stream=sys.stdout): ...
64 # so I use **kwds and sqauwk if something unexpected is passed in.
65 self.stream = sys.stdout
66 if "stream" in kwds:
67 self.stream = kwds["stream"]
68 del kwds["stream"]
69 if kwds:
70 keys = kwds.keys()
71 keys.sort()
72 extras = ", ".join(["%s=%s" % (k, kwds[k]) for k in keys])
73 raise ValueError, "unrecognized keyword args: %s" % extras
Tim Peters2344fae2001-01-15 00:50:52 +000074 if not len(args):
75 arg = None
76 else:
77 arg = args[0]
78 args = args[1:]
79 self.init(arg)
Guido van Rossum68468eb2003-02-27 20:14:51 +000080 self.add(*args)
Tim Peters2344fae2001-01-15 00:50:52 +000081
82 def init(self, arg):
83 self.all_callees = None # calc only if needed
84 self.files = []
85 self.fcn_list = None
86 self.total_tt = 0
87 self.total_calls = 0
88 self.prim_calls = 0
89 self.max_name_len = 0
90 self.top_level = {}
91 self.stats = {}
92 self.sort_arg_dict = {}
93 self.load_stats(arg)
94 trouble = 1
95 try:
96 self.get_top_level_stats()
97 trouble = 0
98 finally:
99 if trouble:
Skip Montanaro262fb922006-04-21 02:31:07 +0000100 print >> self.stream, "Invalid timing data",
101 if self.files: print >> self.stream, self.files[-1],
102 print >> self.stream
Guido van Rossumadb31051994-06-23 11:42:52 +0000103
Tim Peters2344fae2001-01-15 00:50:52 +0000104 def load_stats(self, arg):
105 if not arg: self.stats = {}
Georg Brandl21d900f2006-11-26 19:27:47 +0000106 elif isinstance(arg, basestring):
Tim Peters2344fae2001-01-15 00:50:52 +0000107 f = open(arg, 'rb')
108 self.stats = marshal.load(f)
109 f.close()
110 try:
111 file_stats = os.stat(arg)
Raymond Hettinger32200ae2002-06-01 19:51:15 +0000112 arg = time.ctime(file_stats.st_mtime) + " " + arg
Tim Peters2344fae2001-01-15 00:50:52 +0000113 except: # in case this is not unix
114 pass
115 self.files = [ arg ]
116 elif hasattr(arg, 'create_stats'):
117 arg.create_stats()
118 self.stats = arg.stats
119 arg.stats = {}
120 if not self.stats:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000121 raise TypeError, "Cannot create or construct a %r object from '%r''" % (
122 self.__class__, arg)
Tim Peters2344fae2001-01-15 00:50:52 +0000123 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000124
Tim Peters2344fae2001-01-15 00:50:52 +0000125 def get_top_level_stats(self):
Tim Peters7d016852001-10-08 06:13:19 +0000126 for func, (cc, nc, tt, ct, callers) in self.stats.items():
127 self.total_calls += nc
128 self.prim_calls += cc
129 self.total_tt += tt
Brett Cannonc3ce0e52008-08-03 22:52:42 +0000130 if ("jprofile", 0, "profiler") in callers:
Tim Peters2344fae2001-01-15 00:50:52 +0000131 self.top_level[func] = None
132 if len(func_std_string(func)) > self.max_name_len:
133 self.max_name_len = len(func_std_string(func))
Guido van Rossumadb31051994-06-23 11:42:52 +0000134
Tim Peters2344fae2001-01-15 00:50:52 +0000135 def add(self, *arg_list):
136 if not arg_list: return self
Guido van Rossum68468eb2003-02-27 20:14:51 +0000137 if len(arg_list) > 1: self.add(*arg_list[1:])
Tim Peters2344fae2001-01-15 00:50:52 +0000138 other = arg_list[0]
Tim Peters7d016852001-10-08 06:13:19 +0000139 if type(self) != type(other) or self.__class__ != other.__class__:
Tim Peters2344fae2001-01-15 00:50:52 +0000140 other = Stats(other)
Tim Peters7d016852001-10-08 06:13:19 +0000141 self.files += other.files
142 self.total_calls += other.total_calls
143 self.prim_calls += other.prim_calls
144 self.total_tt += other.total_tt
Raymond Hettingere0d49722002-06-02 18:55:56 +0000145 for func in other.top_level:
Tim Peters2344fae2001-01-15 00:50:52 +0000146 self.top_level[func] = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000147
Tim Peters2344fae2001-01-15 00:50:52 +0000148 if self.max_name_len < other.max_name_len:
149 self.max_name_len = other.max_name_len
Guido van Rossumadb31051994-06-23 11:42:52 +0000150
Tim Peters2344fae2001-01-15 00:50:52 +0000151 self.fcn_list = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000152
Raymond Hettingere0d49722002-06-02 18:55:56 +0000153 for func, stat in other.stats.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000154 if func in self.stats:
Tim Peters2344fae2001-01-15 00:50:52 +0000155 old_func_stat = self.stats[func]
156 else:
157 old_func_stat = (0, 0, 0, 0, {},)
Raymond Hettingere0d49722002-06-02 18:55:56 +0000158 self.stats[func] = add_func_stats(old_func_stat, stat)
Tim Peters2344fae2001-01-15 00:50:52 +0000159 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000160
Fred Drake9c439102003-05-14 14:28:09 +0000161 def dump_stats(self, filename):
162 """Write the profile data to a file we know how to load back."""
163 f = file(filename, 'wb')
164 try:
165 marshal.dump(self.stats, f)
166 finally:
167 f.close()
168
Tim Peters2344fae2001-01-15 00:50:52 +0000169 # list the tuple indices and directions for sorting,
170 # along with some printable description
Tim Peters7d016852001-10-08 06:13:19 +0000171 sort_arg_dict_default = {
172 "calls" : (((1,-1), ), "call count"),
173 "cumulative": (((3,-1), ), "cumulative time"),
174 "file" : (((4, 1), ), "file name"),
175 "line" : (((5, 1), ), "line number"),
176 "module" : (((4, 1), ), "file name"),
177 "name" : (((6, 1), ), "function name"),
178 "nfl" : (((6, 1),(4, 1),(5, 1),), "name/file/line"),
179 "pcalls" : (((0,-1), ), "call count"),
180 "stdname" : (((7, 1), ), "standard name"),
181 "time" : (((2,-1), ), "internal time"),
Tim Peters2344fae2001-01-15 00:50:52 +0000182 }
Guido van Rossumadb31051994-06-23 11:42:52 +0000183
Tim Peters2344fae2001-01-15 00:50:52 +0000184 def get_sort_arg_defs(self):
185 """Expand all abbreviations that are unique."""
186 if not self.sort_arg_dict:
187 self.sort_arg_dict = dict = {}
Tim Peters2344fae2001-01-15 00:50:52 +0000188 bad_list = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000189 for word, tup in self.sort_arg_dict_default.iteritems():
Tim Peters2344fae2001-01-15 00:50:52 +0000190 fragment = word
191 while fragment:
192 if not fragment:
193 break
Raymond Hettinger54f02222002-06-01 14:18:47 +0000194 if fragment in dict:
Tim Peters2344fae2001-01-15 00:50:52 +0000195 bad_list[fragment] = 0
196 break
Raymond Hettingere0d49722002-06-02 18:55:56 +0000197 dict[fragment] = tup
Tim Peters2344fae2001-01-15 00:50:52 +0000198 fragment = fragment[:-1]
Raymond Hettingere0d49722002-06-02 18:55:56 +0000199 for word in bad_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000200 del dict[word]
201 return self.sort_arg_dict
Guido van Rossumadb31051994-06-23 11:42:52 +0000202
Tim Peters2344fae2001-01-15 00:50:52 +0000203 def sort_stats(self, *field):
204 if not field:
205 self.fcn_list = 0
206 return self
207 if len(field) == 1 and type(field[0]) == type(1):
208 # Be compatible with old profiler
Tim Peters7d016852001-10-08 06:13:19 +0000209 field = [ {-1: "stdname",
210 0:"calls",
211 1:"time",
Tim Peters2344fae2001-01-15 00:50:52 +0000212 2: "cumulative" } [ field[0] ] ]
213
214 sort_arg_defs = self.get_sort_arg_defs()
215 sort_tuple = ()
216 self.sort_type = ""
217 connector = ""
218 for word in field:
219 sort_tuple = sort_tuple + sort_arg_defs[word][0]
Tim Peters7d016852001-10-08 06:13:19 +0000220 self.sort_type += connector + sort_arg_defs[word][1]
Tim Peters2344fae2001-01-15 00:50:52 +0000221 connector = ", "
222
223 stats_list = []
Raymond Hettingere0d49722002-06-02 18:55:56 +0000224 for func, (cc, nc, tt, ct, callers) in self.stats.iteritems():
Tim Peters7d016852001-10-08 06:13:19 +0000225 stats_list.append((cc, nc, tt, ct) + func +
226 (func_std_string(func), func))
Tim Peters2344fae2001-01-15 00:50:52 +0000227
Brett Cannonc3ce0e52008-08-03 22:52:42 +0000228 stats_list.sort(key=CmpToKey(TupleComp(sort_tuple).compare))
Tim Peters2344fae2001-01-15 00:50:52 +0000229
230 self.fcn_list = fcn_list = []
231 for tuple in stats_list:
232 fcn_list.append(tuple[-1])
233 return self
234
Tim Peters2344fae2001-01-15 00:50:52 +0000235 def reverse_order(self):
Tim Peters7d016852001-10-08 06:13:19 +0000236 if self.fcn_list:
237 self.fcn_list.reverse()
Tim Peters2344fae2001-01-15 00:50:52 +0000238 return self
239
240 def strip_dirs(self):
241 oldstats = self.stats
242 self.stats = newstats = {}
243 max_name_len = 0
Raymond Hettingere0d49722002-06-02 18:55:56 +0000244 for func, (cc, nc, tt, ct, callers) in oldstats.iteritems():
Tim Peters2344fae2001-01-15 00:50:52 +0000245 newfunc = func_strip_path(func)
246 if len(func_std_string(newfunc)) > max_name_len:
247 max_name_len = len(func_std_string(newfunc))
248 newcallers = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000249 for func2, caller in callers.iteritems():
250 newcallers[func_strip_path(func2)] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000251
Raymond Hettinger54f02222002-06-01 14:18:47 +0000252 if newfunc in newstats:
Tim Peters7d016852001-10-08 06:13:19 +0000253 newstats[newfunc] = add_func_stats(
254 newstats[newfunc],
255 (cc, nc, tt, ct, newcallers))
Tim Peters2344fae2001-01-15 00:50:52 +0000256 else:
257 newstats[newfunc] = (cc, nc, tt, ct, newcallers)
258 old_top = self.top_level
259 self.top_level = new_top = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000260 for func in old_top:
Tim Peters2344fae2001-01-15 00:50:52 +0000261 new_top[func_strip_path(func)] = None
262
263 self.max_name_len = max_name_len
264
265 self.fcn_list = None
266 self.all_callees = None
267 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000268
Tim Peters2344fae2001-01-15 00:50:52 +0000269 def calc_callees(self):
270 if self.all_callees: return
271 self.all_callees = all_callees = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000272 for func, (cc, nc, tt, ct, callers) in self.stats.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000273 if not func in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000274 all_callees[func] = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000275 for func2, caller in callers.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000276 if not func2 in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000277 all_callees[func2] = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000278 all_callees[func2][func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000279 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000280
Tim Peters2344fae2001-01-15 00:50:52 +0000281 #******************************************************************
282 # The following functions support actual printing of reports
283 #******************************************************************
Guido van Rossumadb31051994-06-23 11:42:52 +0000284
Tim Peters2344fae2001-01-15 00:50:52 +0000285 # Optional "amount" is either a line count, or a percentage of lines.
Guido van Rossumadb31051994-06-23 11:42:52 +0000286
Tim Peters2344fae2001-01-15 00:50:52 +0000287 def eval_print_amount(self, sel, list, msg):
288 new_list = list
289 if type(sel) == type(""):
290 new_list = []
291 for func in list:
292 if re.search(sel, func_std_string(func)):
293 new_list.append(func)
294 else:
295 count = len(list)
296 if type(sel) == type(1.0) and 0.0 <= sel < 1.0:
Tim Peters7d016852001-10-08 06:13:19 +0000297 count = int(count * sel + .5)
Tim Peters2344fae2001-01-15 00:50:52 +0000298 new_list = list[:count]
299 elif type(sel) == type(1) and 0 <= sel < count:
300 count = sel
301 new_list = list[:count]
302 if len(list) != len(new_list):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000303 msg = msg + " List reduced from %r to %r due to restriction <%r>\n" % (
304 len(list), len(new_list), sel)
Guido van Rossumadb31051994-06-23 11:42:52 +0000305
Tim Peters2344fae2001-01-15 00:50:52 +0000306 return new_list, msg
Guido van Rossumadb31051994-06-23 11:42:52 +0000307
Tim Peters2344fae2001-01-15 00:50:52 +0000308 def get_print_list(self, sel_list):
309 width = self.max_name_len
310 if self.fcn_list:
311 list = self.fcn_list[:]
312 msg = " Ordered by: " + self.sort_type + '\n'
313 else:
314 list = self.stats.keys()
315 msg = " Random listing order was used\n"
316
317 for selection in sel_list:
Tim Peters7d016852001-10-08 06:13:19 +0000318 list, msg = self.eval_print_amount(selection, list, msg)
Tim Peters2344fae2001-01-15 00:50:52 +0000319
320 count = len(list)
321
322 if not list:
323 return 0, list
Skip Montanaro262fb922006-04-21 02:31:07 +0000324 print >> self.stream, msg
Tim Peters2344fae2001-01-15 00:50:52 +0000325 if count < len(self.stats):
326 width = 0
327 for func in list:
328 if len(func_std_string(func)) > width:
329 width = len(func_std_string(func))
330 return width+2, list
331
332 def print_stats(self, *amount):
333 for filename in self.files:
Skip Montanaro262fb922006-04-21 02:31:07 +0000334 print >> self.stream, filename
335 if self.files: print >> self.stream
Tim Peters7d016852001-10-08 06:13:19 +0000336 indent = ' ' * 8
Raymond Hettingere0d49722002-06-02 18:55:56 +0000337 for func in self.top_level:
Skip Montanaro262fb922006-04-21 02:31:07 +0000338 print >> self.stream, indent, func_get_function_name(func)
Tim Peters2344fae2001-01-15 00:50:52 +0000339
Skip Montanaro262fb922006-04-21 02:31:07 +0000340 print >> self.stream, indent, self.total_calls, "function calls",
Tim Peters2344fae2001-01-15 00:50:52 +0000341 if self.total_calls != self.prim_calls:
Skip Montanaro262fb922006-04-21 02:31:07 +0000342 print >> self.stream, "(%d primitive calls)" % self.prim_calls,
343 print >> self.stream, "in %.3f CPU seconds" % self.total_tt
344 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000345 width, list = self.get_print_list(amount)
346 if list:
347 self.print_title()
348 for func in list:
349 self.print_line(func)
Skip Montanaro262fb922006-04-21 02:31:07 +0000350 print >> self.stream
351 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000352 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000353
Tim Peters2344fae2001-01-15 00:50:52 +0000354 def print_callees(self, *amount):
355 width, list = self.get_print_list(amount)
356 if list:
357 self.calc_callees()
358
359 self.print_call_heading(width, "called...")
360 for func in list:
Raymond Hettinger54f02222002-06-01 14:18:47 +0000361 if func in self.all_callees:
Tim Peters7d016852001-10-08 06:13:19 +0000362 self.print_call_line(width, func, self.all_callees[func])
Tim Peters2344fae2001-01-15 00:50:52 +0000363 else:
364 self.print_call_line(width, func, {})
Skip Montanaro262fb922006-04-21 02:31:07 +0000365 print >> self.stream
366 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000367 return self
368
369 def print_callers(self, *amount):
370 width, list = self.get_print_list(amount)
371 if list:
372 self.print_call_heading(width, "was called by...")
373 for func in list:
374 cc, nc, tt, ct, callers = self.stats[func]
Armin Rigoa871ef22006-02-08 12:53:56 +0000375 self.print_call_line(width, func, callers, "<-")
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_call_heading(self, name_size, column_title):
Skip Montanaro262fb922006-04-21 02:31:07 +0000381 print >> self.stream, "Function ".ljust(name_size) + column_title
Armin Rigoa871ef22006-02-08 12:53:56 +0000382 # print sub-header only if we have new-style callers
383 subheader = False
384 for cc, nc, tt, ct, callers in self.stats.itervalues():
385 if callers:
386 value = callers.itervalues().next()
387 subheader = isinstance(value, tuple)
388 break
389 if subheader:
Skip Montanaro262fb922006-04-21 02:31:07 +0000390 print >> self.stream, " "*name_size + " ncalls tottime cumtime"
Guido van Rossumadb31051994-06-23 11:42:52 +0000391
Armin Rigoa871ef22006-02-08 12:53:56 +0000392 def print_call_line(self, name_size, source, call_dict, arrow="->"):
Skip Montanaro262fb922006-04-21 02:31:07 +0000393 print >> self.stream, func_std_string(source).ljust(name_size) + arrow,
Tim Peters2344fae2001-01-15 00:50:52 +0000394 if not call_dict:
Skip Montanaro262fb922006-04-21 02:31:07 +0000395 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000396 return
397 clist = call_dict.keys()
398 clist.sort()
Tim Peters2344fae2001-01-15 00:50:52 +0000399 indent = ""
400 for func in clist:
401 name = func_std_string(func)
Armin Rigoa871ef22006-02-08 12:53:56 +0000402 value = call_dict[func]
403 if isinstance(value, tuple):
404 nc, cc, tt, ct = value
405 if nc != cc:
406 substats = '%d/%d' % (nc, cc)
407 else:
408 substats = '%d' % (nc,)
409 substats = '%s %s %s %s' % (substats.rjust(7+2*len(indent)),
410 f8(tt), f8(ct), name)
411 left_width = name_size + 1
412 else:
413 substats = '%s(%r) %s' % (name, value, f8(self.stats[func][3]))
414 left_width = name_size + 3
Skip Montanaro262fb922006-04-21 02:31:07 +0000415 print >> self.stream, indent*left_width + substats
Tim Peters2344fae2001-01-15 00:50:52 +0000416 indent = " "
417
Tim Peters2344fae2001-01-15 00:50:52 +0000418 def print_title(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000419 print >> self.stream, ' ncalls tottime percall cumtime percall',
420 print >> self.stream, 'filename:lineno(function)'
Tim Peters2344fae2001-01-15 00:50:52 +0000421
422 def print_line(self, func): # hack : should print percentages
423 cc, nc, tt, ct, callers = self.stats[func]
Tim Peters7d016852001-10-08 06:13:19 +0000424 c = str(nc)
Tim Peters2344fae2001-01-15 00:50:52 +0000425 if nc != cc:
Tim Peters7d016852001-10-08 06:13:19 +0000426 c = c + '/' + str(cc)
Skip Montanaro262fb922006-04-21 02:31:07 +0000427 print >> self.stream, c.rjust(9),
428 print >> self.stream, f8(tt),
Tim Peters2344fae2001-01-15 00:50:52 +0000429 if nc == 0:
Skip Montanaro262fb922006-04-21 02:31:07 +0000430 print >> self.stream, ' '*8,
Tim Peters2344fae2001-01-15 00:50:52 +0000431 else:
Ezio Melotti8dc04a42010-08-02 00:24:26 +0000432 print >> self.stream, f8(float(tt)/nc),
Skip Montanaro262fb922006-04-21 02:31:07 +0000433 print >> self.stream, f8(ct),
Tim Peters2344fae2001-01-15 00:50:52 +0000434 if cc == 0:
Skip Montanaro262fb922006-04-21 02:31:07 +0000435 print >> self.stream, ' '*8,
Tim Peters2344fae2001-01-15 00:50:52 +0000436 else:
Ezio Melotti8dc04a42010-08-02 00:24:26 +0000437 print >> self.stream, f8(float(ct)/cc),
Skip Montanaro262fb922006-04-21 02:31:07 +0000438 print >> self.stream, func_std_string(func)
Tim Peters2344fae2001-01-15 00:50:52 +0000439
Guido van Rossumadb31051994-06-23 11:42:52 +0000440class TupleComp:
Tim Peters2344fae2001-01-15 00:50:52 +0000441 """This class provides a generic function for comparing any two tuples.
442 Each instance records a list of tuple-indices (from most significant
443 to least significant), and sort direction (ascending or decending) for
444 each tuple-index. The compare functions can then be used as the function
445 argument to the system sort() function when a list of tuples need to be
446 sorted in the instances order."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000447
Tim Peters2344fae2001-01-15 00:50:52 +0000448 def __init__(self, comp_select_list):
449 self.comp_select_list = comp_select_list
Guido van Rossumadb31051994-06-23 11:42:52 +0000450
Tim Peters2344fae2001-01-15 00:50:52 +0000451 def compare (self, left, right):
452 for index, direction in self.comp_select_list:
453 l = left[index]
454 r = right[index]
455 if l < r:
456 return -direction
457 if l > r:
458 return direction
459 return 0
Guido van Rossumadb31051994-06-23 11:42:52 +0000460
Brett Cannonc3ce0e52008-08-03 22:52:42 +0000461def CmpToKey(mycmp):
462 """Convert a cmp= function into a key= function"""
463 class K(object):
464 def __init__(self, obj):
465 self.obj = obj
466 def __lt__(self, other):
467 return mycmp(self.obj, other.obj) == -1
468 return K
469
470
Guido van Rossumadb31051994-06-23 11:42:52 +0000471#**************************************************************************
Tim Peters7d016852001-10-08 06:13:19 +0000472# func_name is a triple (file:string, line:int, name:string)
Guido van Rossumadb31051994-06-23 11:42:52 +0000473
474def func_strip_path(func_name):
Fred Drake9c439102003-05-14 14:28:09 +0000475 filename, line, name = func_name
476 return os.path.basename(filename), line, name
Guido van Rossumadb31051994-06-23 11:42:52 +0000477
478def func_get_function_name(func):
Tim Peters2344fae2001-01-15 00:50:52 +0000479 return func[2]
Guido van Rossumadb31051994-06-23 11:42:52 +0000480
481def func_std_string(func_name): # match what old profile produced
Armin Rigoa871ef22006-02-08 12:53:56 +0000482 if func_name[:2] == ('~', 0):
483 # special case for built-in functions
484 name = func_name[2]
485 if name.startswith('<') and name.endswith('>'):
486 return '{%s}' % name[1:-1]
487 else:
488 return name
489 else:
490 return "%s:%d(%s)" % func_name
Guido van Rossumadb31051994-06-23 11:42:52 +0000491
492#**************************************************************************
493# The following functions combine statists for pairs functions.
494# The bulk of the processing involves correctly handling "call" lists,
Tim Peters2344fae2001-01-15 00:50:52 +0000495# such as callers and callees.
Guido van Rossumadb31051994-06-23 11:42:52 +0000496#**************************************************************************
497
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000498def add_func_stats(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000499 """Add together all the stats for two profile entries."""
500 cc, nc, tt, ct, callers = source
501 t_cc, t_nc, t_tt, t_ct, t_callers = target
Tim Peters7d016852001-10-08 06:13:19 +0000502 return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct,
Tim Peters2344fae2001-01-15 00:50:52 +0000503 add_callers(t_callers, callers))
Guido van Rossumadb31051994-06-23 11:42:52 +0000504
Guido van Rossumadb31051994-06-23 11:42:52 +0000505def add_callers(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000506 """Combine two caller lists in a single list."""
507 new_callers = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000508 for func, caller in target.iteritems():
509 new_callers[func] = caller
510 for func, caller in source.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000511 if func in new_callers:
Georg Brandl800a3542010-08-02 17:40:28 +0000512 if isinstance(caller, tuple):
513 # format used by cProfile
514 new_callers[func] = tuple([i[0] + i[1] for i in
515 zip(caller, new_callers[func])])
516 else:
517 # format used by profile
518 new_callers[func] += caller
Tim Peters2344fae2001-01-15 00:50:52 +0000519 else:
Raymond Hettingere0d49722002-06-02 18:55:56 +0000520 new_callers[func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000521 return new_callers
Guido van Rossumadb31051994-06-23 11:42:52 +0000522
Guido van Rossumadb31051994-06-23 11:42:52 +0000523def count_calls(callers):
Tim Peters2344fae2001-01-15 00:50:52 +0000524 """Sum the caller statistics to get total number of calls received."""
525 nc = 0
Raymond Hettingere0d49722002-06-02 18:55:56 +0000526 for calls in callers.itervalues():
527 nc += calls
Tim Peters2344fae2001-01-15 00:50:52 +0000528 return nc
Guido van Rossumadb31051994-06-23 11:42:52 +0000529
530#**************************************************************************
531# The following functions support printing of reports
532#**************************************************************************
533
534def f8(x):
Tim Peters7d016852001-10-08 06:13:19 +0000535 return "%8.3f" % x
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000536
537#**************************************************************************
538# Statistics browser added by ESR, April 2001
539#**************************************************************************
540
541if __name__ == '__main__':
542 import cmd
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000543 try:
544 import readline
Fred Drakee8187612001-05-11 19:21:41 +0000545 except ImportError:
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000546 pass
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000547
548 class ProfileBrowser(cmd.Cmd):
549 def __init__(self, profile=None):
Martin v. Löwis66b6e192001-07-28 14:44:03 +0000550 cmd.Cmd.__init__(self)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000551 self.prompt = "% "
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000552 if profile is not None:
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000553 self.stats = Stats(profile)
Neal Norwitze588c2b2006-06-11 07:27:56 +0000554 self.stream = self.stats.stream
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000555 else:
556 self.stats = None
Neal Norwitze588c2b2006-06-11 07:27:56 +0000557 self.stream = sys.stdout
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000558
559 def generic(self, fn, line):
560 args = line.split()
561 processed = []
562 for term in args:
563 try:
564 processed.append(int(term))
565 continue
566 except ValueError:
567 pass
568 try:
569 frac = float(term)
570 if frac > 1 or frac < 0:
Skip Montanaro262fb922006-04-21 02:31:07 +0000571 print >> self.stream, "Fraction argument must be in [0, 1]"
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000572 continue
573 processed.append(frac)
574 continue
575 except ValueError:
576 pass
577 processed.append(term)
578 if self.stats:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000579 getattr(self.stats, fn)(*processed)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000580 else:
Skip Montanaro262fb922006-04-21 02:31:07 +0000581 print >> self.stream, "No statistics object is loaded."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000582 return 0
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000583 def generic_help(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000584 print >> self.stream, "Arguments may be:"
585 print >> self.stream, "* An integer maximum number of entries to print."
586 print >> self.stream, "* A decimal fractional number between 0 and 1, controlling"
587 print >> self.stream, " what fraction of selected entries to print."
588 print >> self.stream, "* A regular expression; only entries with function names"
589 print >> self.stream, " that match it are printed."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000590
591 def do_add(self, line):
Georg Brandld9ede202010-08-01 22:13:33 +0000592 if self.stats:
593 self.stats.add(line)
594 else:
595 print >> self.stream, "No statistics object is loaded."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000596 return 0
597 def help_add(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000598 print >> self.stream, "Add profile info from given file to current statistics object."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000599
600 def do_callees(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000601 return self.generic('print_callees', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000602 def help_callees(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000603 print >> self.stream, "Print callees statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000604 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000605
606 def do_callers(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000607 return self.generic('print_callers', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000608 def help_callers(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000609 print >> self.stream, "Print callers statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000610 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000611
612 def do_EOF(self, line):
Skip Montanaro262fb922006-04-21 02:31:07 +0000613 print >> self.stream, ""
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000614 return 1
615 def help_EOF(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_quit(self, line):
619 return 1
620 def help_quit(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000621 print >> self.stream, "Leave the profile brower."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000622
623 def do_read(self, line):
624 if line:
625 try:
626 self.stats = Stats(line)
627 except IOError, args:
Skip Montanaro262fb922006-04-21 02:31:07 +0000628 print >> self.stream, args[1]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000629 return
Georg Brandld9ede202010-08-01 22:13:33 +0000630 except Exception as err:
631 print >> self.stream, err.__class__.__name__ + ':', err
632 return
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000633 self.prompt = line + "% "
Martin v. Löwis22adac52001-06-07 05:49:05 +0000634 elif len(self.prompt) > 2:
Georg Brandld9ede202010-08-01 22:13:33 +0000635 line = self.prompt[:-2]
636 self.do_read(line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000637 else:
Skip Montanaro262fb922006-04-21 02:31:07 +0000638 print >> self.stream, "No statistics object is current -- cannot reload."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000639 return 0
640 def help_read(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000641 print >> self.stream, "Read in profile data from a specified file."
Georg Brandld9ede202010-08-01 22:13:33 +0000642 print >> self.stream, "Without argument, reload the current file."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000643
644 def do_reverse(self, line):
Georg Brandld9ede202010-08-01 22:13:33 +0000645 if self.stats:
646 self.stats.reverse_order()
647 else:
648 print >> self.stream, "No statistics object is loaded."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000649 return 0
650 def help_reverse(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000651 print >> self.stream, "Reverse the sort order of the profiling report."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000652
653 def do_sort(self, line):
Georg Brandld9ede202010-08-01 22:13:33 +0000654 if not self.stats:
655 print >> self.stream, "No statistics object is loaded."
656 return
Raymond Hettingere0d49722002-06-02 18:55:56 +0000657 abbrevs = self.stats.get_sort_arg_defs()
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000658 if line and not filter(lambda x,a=abbrevs: x not in a,line.split()):
Guido van Rossum68468eb2003-02-27 20:14:51 +0000659 self.stats.sort_stats(*line.split())
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000660 else:
Skip Montanaro262fb922006-04-21 02:31:07 +0000661 print >> self.stream, "Valid sort keys (unique prefixes are accepted):"
Raymond Hettingere0d49722002-06-02 18:55:56 +0000662 for (key, value) in Stats.sort_arg_dict_default.iteritems():
Skip Montanaro262fb922006-04-21 02:31:07 +0000663 print >> self.stream, "%s -- %s" % (key, value[1])
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000664 return 0
665 def help_sort(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000666 print >> self.stream, "Sort profile data according to specified keys."
667 print >> self.stream, "(Typing `sort' without arguments lists valid keys.)"
Martin v. Löwis27c430e2001-07-30 10:21:13 +0000668 def complete_sort(self, text, *args):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000669 return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000670
671 def do_stats(self, line):
672 return self.generic('print_stats', line)
673 def help_stats(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000674 print >> self.stream, "Print statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000675 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000676
677 def do_strip(self, line):
Georg Brandld9ede202010-08-01 22:13:33 +0000678 if self.stats:
679 self.stats.strip_dirs()
680 else:
681 print >> self.stream, "No statistics object is loaded."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000682 def help_strip(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000683 print >> self.stream, "Strip leading path information from filenames in the report."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000684
Georg Brandld9ede202010-08-01 22:13:33 +0000685 def help_help(self):
686 print >> self.stream, "Show help for a given command."
687
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000688 def postcmd(self, stop, line):
689 if stop:
690 return stop
691 return None
692
693 import sys
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000694 if len(sys.argv) > 1:
695 initprofile = sys.argv[1]
696 else:
697 initprofile = None
698 try:
Neal Norwitze588c2b2006-06-11 07:27:56 +0000699 browser = ProfileBrowser(initprofile)
700 print >> browser.stream, "Welcome to the profile statistics browser."
701 browser.cmdloop()
702 print >> browser.stream, "Goodbye."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000703 except KeyboardInterrupt:
704 pass
705
706# That's all, folks.