blob: 74360e38dbccb3976414dfeda2d78b5e226e9ec5 [file] [log] [blame]
Guido van Rossum54f22ed2000-02-04 15:10:34 +00001"""Class for printing reports on profiled python code."""
2
Benjamin Petersonafa44a82011-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 Petersonafa44a82011-06-27 09:14:34 -05007# Copyright Disney Enterprises, Inc. All Rights Reserved.
8# Licensed to PSF under a Contributor Agreement
9#
10# 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
13#
14# 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
22
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Guido van Rossumadb31051994-06-23 11:42:52 +000028
Skip Montanaroc62c81e2001-02-12 02:00:42 +000029__all__ = ["Stats"]
30
Guido van Rossumadb31051994-06-23 11:42:52 +000031class Stats:
Tim Peters2344fae2001-01-15 00:50:52 +000032 """This class is used for creating reports from data generated by the
33 Profile class. It is a "friend" of that class, and imports data either
34 by direct access to members of Profile class, or by reading in a dictionary
35 that was emitted (via marshal) from the Profile class.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000036
Tim Peters2344fae2001-01-15 00:50:52 +000037 The big change from the previous Profiler (in terms of raw functionality)
38 is that an "add()" method has been provided to combine Stats from
39 several distinct profile runs. Both the constructor and the add()
40 method now take arbitrarily many file names as arguments.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000041
Tim Peters2344fae2001-01-15 00:50:52 +000042 All the print methods now take an argument that indicates how many lines
43 to print. If the arg is a floating point number between 0 and 1.0, then
44 it is taken as a decimal percentage of the available lines to be printed
45 (e.g., .1 means print 10% of all available lines). If it is an integer,
46 it is taken to mean the number of lines of data that you wish to have
47 printed.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000048
Tim Peters2344fae2001-01-15 00:50:52 +000049 The sort_stats() method now processes some additional options (i.e., in
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000050 addition to the old -1, 0, 1, or 2). It takes an arbitrary number of
51 quoted strings to select the sort order. For example sort_stats('time',
52 'name') sorts on the major key of 'internal function time', and on the
53 minor key of 'the name of the function'. Look at the two tables in
54 sort_stats() and get_sort_arg_defs(self) for more examples.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000055
Georg Brandl4009c9e2010-10-06 08:26:09 +000056 All methods return self, so you can string together commands like:
Tim Peters2344fae2001-01-15 00:50:52 +000057 Stats('foo', 'goo').strip_dirs().sort_stats('calls').\
58 print_stats(5).print_callers(5)
59 """
60
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000061 def __init__(self, *args, **kwds):
Ezio Melotti13925002011-03-16 11:05:33 +020062 # I can't figure out how to explicitly specify a stream keyword arg
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000063 # with *args:
64 # def __init__(self, *args, stream=sys.stdout): ...
65 # so I use **kwds and sqauwk if something unexpected is passed in.
66 self.stream = sys.stdout
67 if "stream" in kwds:
68 self.stream = kwds["stream"]
69 del kwds["stream"]
70 if kwds:
71 keys = kwds.keys()
72 keys.sort()
73 extras = ", ".join(["%s=%s" % (k, kwds[k]) for k in keys])
Collin Winterce36ad82007-08-30 01:19:48 +000074 raise ValueError("unrecognized keyword args: %s" % extras)
Tim Peters2344fae2001-01-15 00:50:52 +000075 if not len(args):
76 arg = None
77 else:
78 arg = args[0]
79 args = args[1:]
80 self.init(arg)
Guido van Rossum68468eb2003-02-27 20:14:51 +000081 self.add(*args)
Tim Peters2344fae2001-01-15 00:50:52 +000082
83 def init(self, arg):
84 self.all_callees = None # calc only if needed
85 self.files = []
86 self.fcn_list = None
87 self.total_tt = 0
88 self.total_calls = 0
89 self.prim_calls = 0
90 self.max_name_len = 0
91 self.top_level = {}
92 self.stats = {}
93 self.sort_arg_dict = {}
94 self.load_stats(arg)
95 trouble = 1
96 try:
97 self.get_top_level_stats()
98 trouble = 0
99 finally:
100 if trouble:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000101 print("Invalid timing data", end=' ', file=self.stream)
102 if self.files: print(self.files[-1], end=' ', file=self.stream)
103 print(file=self.stream)
Guido van Rossumadb31051994-06-23 11:42:52 +0000104
Tim Peters2344fae2001-01-15 00:50:52 +0000105 def load_stats(self, arg):
106 if not arg: self.stats = {}
Guido van Rossum3172c5d2007-10-16 18:12:55 +0000107 elif isinstance(arg, str):
Tim Peters2344fae2001-01-15 00:50:52 +0000108 f = open(arg, 'rb')
109 self.stats = marshal.load(f)
110 f.close()
111 try:
112 file_stats = os.stat(arg)
Raymond Hettinger32200ae2002-06-01 19:51:15 +0000113 arg = time.ctime(file_stats.st_mtime) + " " + arg
Tim Peters2344fae2001-01-15 00:50:52 +0000114 except: # in case this is not unix
115 pass
116 self.files = [ arg ]
117 elif hasattr(arg, 'create_stats'):
118 arg.create_stats()
119 self.stats = arg.stats
120 arg.stats = {}
121 if not self.stats:
Collin Winterce36ad82007-08-30 01:19:48 +0000122 raise TypeError("Cannot create or construct a %r object from '%r''"
123 % (self.__class__, arg))
Tim Peters2344fae2001-01-15 00:50:52 +0000124 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000125
Tim Peters2344fae2001-01-15 00:50:52 +0000126 def get_top_level_stats(self):
Tim Peters7d016852001-10-08 06:13:19 +0000127 for func, (cc, nc, tt, ct, callers) in self.stats.items():
128 self.total_calls += nc
129 self.prim_calls += cc
130 self.total_tt += tt
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000131 if ("jprofile", 0, "profiler") in callers:
Tim Peters2344fae2001-01-15 00:50:52 +0000132 self.top_level[func] = None
133 if len(func_std_string(func)) > self.max_name_len:
134 self.max_name_len = len(func_std_string(func))
Guido van Rossumadb31051994-06-23 11:42:52 +0000135
Tim Peters2344fae2001-01-15 00:50:52 +0000136 def add(self, *arg_list):
137 if not arg_list: return self
Guido van Rossum68468eb2003-02-27 20:14:51 +0000138 if len(arg_list) > 1: self.add(*arg_list[1:])
Tim Peters2344fae2001-01-15 00:50:52 +0000139 other = arg_list[0]
Georg Brandl4009c9e2010-10-06 08:26:09 +0000140 if type(self) != type(other):
Tim Peters2344fae2001-01-15 00:50:52 +0000141 other = Stats(other)
Tim Peters7d016852001-10-08 06:13:19 +0000142 self.files += other.files
143 self.total_calls += other.total_calls
144 self.prim_calls += other.prim_calls
145 self.total_tt += other.total_tt
Raymond Hettingere0d49722002-06-02 18:55:56 +0000146 for func in other.top_level:
Tim Peters2344fae2001-01-15 00:50:52 +0000147 self.top_level[func] = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000148
Tim Peters2344fae2001-01-15 00:50:52 +0000149 if self.max_name_len < other.max_name_len:
150 self.max_name_len = other.max_name_len
Guido van Rossumadb31051994-06-23 11:42:52 +0000151
Tim Peters2344fae2001-01-15 00:50:52 +0000152 self.fcn_list = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000153
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000154 for func, stat in other.stats.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000155 if func in self.stats:
Tim Peters2344fae2001-01-15 00:50:52 +0000156 old_func_stat = self.stats[func]
157 else:
158 old_func_stat = (0, 0, 0, 0, {},)
Raymond Hettingere0d49722002-06-02 18:55:56 +0000159 self.stats[func] = add_func_stats(old_func_stat, stat)
Tim Peters2344fae2001-01-15 00:50:52 +0000160 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000161
Fred Drake9c439102003-05-14 14:28:09 +0000162 def dump_stats(self, filename):
163 """Write the profile data to a file we know how to load back."""
Alex Martelli01c77c62006-08-24 02:58:11 +0000164 f = open(filename, 'wb')
Fred Drake9c439102003-05-14 14:28:09 +0000165 try:
166 marshal.dump(self.stats, f)
167 finally:
168 f.close()
169
Tim Peters2344fae2001-01-15 00:50:52 +0000170 # list the tuple indices and directions for sorting,
171 # along with some printable description
Tim Peters7d016852001-10-08 06:13:19 +0000172 sort_arg_dict_default = {
173 "calls" : (((1,-1), ), "call count"),
174 "cumulative": (((3,-1), ), "cumulative time"),
175 "file" : (((4, 1), ), "file name"),
176 "line" : (((5, 1), ), "line number"),
177 "module" : (((4, 1), ), "file name"),
178 "name" : (((6, 1), ), "function name"),
179 "nfl" : (((6, 1),(4, 1),(5, 1),), "name/file/line"),
180 "pcalls" : (((0,-1), ), "call count"),
181 "stdname" : (((7, 1), ), "standard name"),
182 "time" : (((2,-1), ), "internal time"),
Tim Peters2344fae2001-01-15 00:50:52 +0000183 }
Guido van Rossumadb31051994-06-23 11:42:52 +0000184
Tim Peters2344fae2001-01-15 00:50:52 +0000185 def get_sort_arg_defs(self):
186 """Expand all abbreviations that are unique."""
187 if not self.sort_arg_dict:
188 self.sort_arg_dict = dict = {}
Tim Peters2344fae2001-01-15 00:50:52 +0000189 bad_list = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000190 for word, tup in self.sort_arg_dict_default.items():
Tim Peters2344fae2001-01-15 00:50:52 +0000191 fragment = word
192 while fragment:
193 if not fragment:
194 break
Raymond Hettinger54f02222002-06-01 14:18:47 +0000195 if fragment in dict:
Tim Peters2344fae2001-01-15 00:50:52 +0000196 bad_list[fragment] = 0
197 break
Raymond Hettingere0d49722002-06-02 18:55:56 +0000198 dict[fragment] = tup
Tim Peters2344fae2001-01-15 00:50:52 +0000199 fragment = fragment[:-1]
Raymond Hettingere0d49722002-06-02 18:55:56 +0000200 for word in bad_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000201 del dict[word]
202 return self.sort_arg_dict
Guido van Rossumadb31051994-06-23 11:42:52 +0000203
Tim Peters2344fae2001-01-15 00:50:52 +0000204 def sort_stats(self, *field):
205 if not field:
206 self.fcn_list = 0
207 return self
Georg Brandl4009c9e2010-10-06 08:26:09 +0000208 if len(field) == 1 and isinstance(field[0], int):
Tim Peters2344fae2001-01-15 00:50:52 +0000209 # Be compatible with old profiler
Tim Peters7d016852001-10-08 06:13:19 +0000210 field = [ {-1: "stdname",
Georg Brandl4009c9e2010-10-06 08:26:09 +0000211 0: "calls",
212 1: "time",
213 2: "cumulative"}[field[0]] ]
Tim Peters2344fae2001-01-15 00:50:52 +0000214
215 sort_arg_defs = self.get_sort_arg_defs()
216 sort_tuple = ()
217 self.sort_type = ""
218 connector = ""
219 for word in field:
220 sort_tuple = sort_tuple + sort_arg_defs[word][0]
Tim Peters7d016852001-10-08 06:13:19 +0000221 self.sort_type += connector + sort_arg_defs[word][1]
Tim Peters2344fae2001-01-15 00:50:52 +0000222 connector = ", "
223
224 stats_list = []
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000225 for func, (cc, nc, tt, ct, callers) in self.stats.items():
Tim Peters7d016852001-10-08 06:13:19 +0000226 stats_list.append((cc, nc, tt, ct) + func +
227 (func_std_string(func), func))
Tim Peters2344fae2001-01-15 00:50:52 +0000228
Raymond Hettinger70b64fc2008-01-30 20:15:17 +0000229 stats_list.sort(key=CmpToKey(TupleComp(sort_tuple).compare))
Tim Peters2344fae2001-01-15 00:50:52 +0000230
231 self.fcn_list = fcn_list = []
232 for tuple in stats_list:
233 fcn_list.append(tuple[-1])
234 return self
235
Tim Peters2344fae2001-01-15 00:50:52 +0000236 def reverse_order(self):
Tim Peters7d016852001-10-08 06:13:19 +0000237 if self.fcn_list:
238 self.fcn_list.reverse()
Tim Peters2344fae2001-01-15 00:50:52 +0000239 return self
240
241 def strip_dirs(self):
242 oldstats = self.stats
243 self.stats = newstats = {}
244 max_name_len = 0
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000245 for func, (cc, nc, tt, ct, callers) in oldstats.items():
Tim Peters2344fae2001-01-15 00:50:52 +0000246 newfunc = func_strip_path(func)
247 if len(func_std_string(newfunc)) > max_name_len:
248 max_name_len = len(func_std_string(newfunc))
249 newcallers = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000250 for func2, caller in callers.items():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000251 newcallers[func_strip_path(func2)] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000252
Raymond Hettinger54f02222002-06-01 14:18:47 +0000253 if newfunc in newstats:
Tim Peters7d016852001-10-08 06:13:19 +0000254 newstats[newfunc] = add_func_stats(
255 newstats[newfunc],
256 (cc, nc, tt, ct, newcallers))
Tim Peters2344fae2001-01-15 00:50:52 +0000257 else:
258 newstats[newfunc] = (cc, nc, tt, ct, newcallers)
259 old_top = self.top_level
260 self.top_level = new_top = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000261 for func in old_top:
Tim Peters2344fae2001-01-15 00:50:52 +0000262 new_top[func_strip_path(func)] = None
263
264 self.max_name_len = max_name_len
265
266 self.fcn_list = None
267 self.all_callees = None
268 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000269
Tim Peters2344fae2001-01-15 00:50:52 +0000270 def calc_callees(self):
271 if self.all_callees: return
272 self.all_callees = all_callees = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000273 for func, (cc, nc, tt, ct, callers) in self.stats.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000274 if not func in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000275 all_callees[func] = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000276 for func2, caller in callers.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000277 if not func2 in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000278 all_callees[func2] = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000279 all_callees[func2][func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000280 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000281
Tim Peters2344fae2001-01-15 00:50:52 +0000282 #******************************************************************
283 # The following functions support actual printing of reports
284 #******************************************************************
Guido van Rossumadb31051994-06-23 11:42:52 +0000285
Tim Peters2344fae2001-01-15 00:50:52 +0000286 # Optional "amount" is either a line count, or a percentage of lines.
Guido van Rossumadb31051994-06-23 11:42:52 +0000287
Tim Peters2344fae2001-01-15 00:50:52 +0000288 def eval_print_amount(self, sel, list, msg):
289 new_list = list
Georg Brandl4009c9e2010-10-06 08:26:09 +0000290 if isinstance(sel, str):
291 try:
292 rex = re.compile(sel)
293 except re.error:
294 msg += " <Invalid regular expression %r>\n" % sel
295 return new_list, msg
Tim Peters2344fae2001-01-15 00:50:52 +0000296 new_list = []
297 for func in list:
Georg Brandl4009c9e2010-10-06 08:26:09 +0000298 if rex.search(func_std_string(func)):
Tim Peters2344fae2001-01-15 00:50:52 +0000299 new_list.append(func)
300 else:
301 count = len(list)
Georg Brandl4009c9e2010-10-06 08:26:09 +0000302 if isinstance(sel, float) and 0.0 <= sel < 1.0:
Tim Peters7d016852001-10-08 06:13:19 +0000303 count = int(count * sel + .5)
Tim Peters2344fae2001-01-15 00:50:52 +0000304 new_list = list[:count]
Georg Brandl4009c9e2010-10-06 08:26:09 +0000305 elif isinstance(sel, int) and 0 <= sel < count:
Tim Peters2344fae2001-01-15 00:50:52 +0000306 count = sel
307 new_list = list[:count]
308 if len(list) != len(new_list):
Georg Brandl4009c9e2010-10-06 08:26:09 +0000309 msg += " List reduced from %r to %r due to restriction <%r>\n" % (
310 len(list), len(new_list), sel)
Guido van Rossumadb31051994-06-23 11:42:52 +0000311
Tim Peters2344fae2001-01-15 00:50:52 +0000312 return new_list, msg
Guido van Rossumadb31051994-06-23 11:42:52 +0000313
Tim Peters2344fae2001-01-15 00:50:52 +0000314 def get_print_list(self, sel_list):
315 width = self.max_name_len
316 if self.fcn_list:
Georg Brandl4009c9e2010-10-06 08:26:09 +0000317 stat_list = self.fcn_list[:]
Tim Peters2344fae2001-01-15 00:50:52 +0000318 msg = " Ordered by: " + self.sort_type + '\n'
319 else:
Georg Brandl4009c9e2010-10-06 08:26:09 +0000320 stat_list = list(self.stats.keys())
Tim Peters2344fae2001-01-15 00:50:52 +0000321 msg = " Random listing order was used\n"
322
323 for selection in sel_list:
Georg Brandl4009c9e2010-10-06 08:26:09 +0000324 stat_list, msg = self.eval_print_amount(selection, stat_list, msg)
Tim Peters2344fae2001-01-15 00:50:52 +0000325
Georg Brandl4009c9e2010-10-06 08:26:09 +0000326 count = len(stat_list)
Tim Peters2344fae2001-01-15 00:50:52 +0000327
Georg Brandl4009c9e2010-10-06 08:26:09 +0000328 if not stat_list:
329 return 0, stat_list
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000330 print(msg, file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000331 if count < len(self.stats):
332 width = 0
Georg Brandl4009c9e2010-10-06 08:26:09 +0000333 for func in stat_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000334 if len(func_std_string(func)) > width:
335 width = len(func_std_string(func))
Georg Brandl4009c9e2010-10-06 08:26:09 +0000336 return width+2, stat_list
Tim Peters2344fae2001-01-15 00:50:52 +0000337
338 def print_stats(self, *amount):
339 for filename in self.files:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000340 print(filename, file=self.stream)
341 if self.files: print(file=self.stream)
Tim Peters7d016852001-10-08 06:13:19 +0000342 indent = ' ' * 8
Raymond Hettingere0d49722002-06-02 18:55:56 +0000343 for func in self.top_level:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000344 print(indent, func_get_function_name(func), file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000345
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000346 print(indent, self.total_calls, "function calls", end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000347 if self.total_calls != self.prim_calls:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000348 print("(%d primitive calls)" % self.prim_calls, end=' ', file=self.stream)
Senthil Kumarane062ba42010-11-20 17:08:19 +0000349 print("in %.3f seconds" % self.total_tt, file=self.stream)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000350 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000351 width, list = self.get_print_list(amount)
352 if list:
353 self.print_title()
354 for func in list:
355 self.print_line(func)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000356 print(file=self.stream)
357 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000358 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000359
Tim Peters2344fae2001-01-15 00:50:52 +0000360 def print_callees(self, *amount):
361 width, list = self.get_print_list(amount)
362 if list:
363 self.calc_callees()
364
365 self.print_call_heading(width, "called...")
366 for func in list:
Raymond Hettinger54f02222002-06-01 14:18:47 +0000367 if func in self.all_callees:
Tim Peters7d016852001-10-08 06:13:19 +0000368 self.print_call_line(width, func, self.all_callees[func])
Tim Peters2344fae2001-01-15 00:50:52 +0000369 else:
370 self.print_call_line(width, func, {})
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000371 print(file=self.stream)
372 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000373 return self
374
375 def print_callers(self, *amount):
376 width, list = self.get_print_list(amount)
377 if list:
378 self.print_call_heading(width, "was called by...")
379 for func in list:
380 cc, nc, tt, ct, callers = self.stats[func]
Armin Rigoa871ef22006-02-08 12:53:56 +0000381 self.print_call_line(width, func, callers, "<-")
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000382 print(file=self.stream)
383 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000384 return self
385
386 def print_call_heading(self, name_size, column_title):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000387 print("Function ".ljust(name_size) + column_title, file=self.stream)
Armin Rigoa871ef22006-02-08 12:53:56 +0000388 # print sub-header only if we have new-style callers
389 subheader = False
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000390 for cc, nc, tt, ct, callers in self.stats.values():
Armin Rigoa871ef22006-02-08 12:53:56 +0000391 if callers:
Georg Brandla18af4e2007-04-21 15:47:16 +0000392 value = next(iter(callers.values()))
Armin Rigoa871ef22006-02-08 12:53:56 +0000393 subheader = isinstance(value, tuple)
394 break
395 if subheader:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000396 print(" "*name_size + " ncalls tottime cumtime", file=self.stream)
Guido van Rossumadb31051994-06-23 11:42:52 +0000397
Armin Rigoa871ef22006-02-08 12:53:56 +0000398 def print_call_line(self, name_size, source, call_dict, arrow="->"):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000399 print(func_std_string(source).ljust(name_size) + arrow, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000400 if not call_dict:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000401 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000402 return
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000403 clist = sorted(call_dict.keys())
Tim Peters2344fae2001-01-15 00:50:52 +0000404 indent = ""
405 for func in clist:
406 name = func_std_string(func)
Armin Rigoa871ef22006-02-08 12:53:56 +0000407 value = call_dict[func]
408 if isinstance(value, tuple):
409 nc, cc, tt, ct = value
410 if nc != cc:
411 substats = '%d/%d' % (nc, cc)
412 else:
413 substats = '%d' % (nc,)
414 substats = '%s %s %s %s' % (substats.rjust(7+2*len(indent)),
415 f8(tt), f8(ct), name)
416 left_width = name_size + 1
417 else:
418 substats = '%s(%r) %s' % (name, value, f8(self.stats[func][3]))
419 left_width = name_size + 3
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000420 print(indent*left_width + substats, file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000421 indent = " "
422
Tim Peters2344fae2001-01-15 00:50:52 +0000423 def print_title(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000424 print(' ncalls tottime percall cumtime percall', end=' ', file=self.stream)
425 print('filename:lineno(function)', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000426
427 def print_line(self, func): # hack : should print percentages
428 cc, nc, tt, ct, callers = self.stats[func]
Tim Peters7d016852001-10-08 06:13:19 +0000429 c = str(nc)
Tim Peters2344fae2001-01-15 00:50:52 +0000430 if nc != cc:
Tim Peters7d016852001-10-08 06:13:19 +0000431 c = c + '/' + str(cc)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000432 print(c.rjust(9), end=' ', file=self.stream)
433 print(f8(tt), end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000434 if nc == 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000435 print(' '*8, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000436 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000437 print(f8(tt/nc), end=' ', file=self.stream)
438 print(f8(ct), end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000439 if cc == 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000440 print(' '*8, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000441 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000442 print(f8(ct/cc), end=' ', file=self.stream)
443 print(func_std_string(func), file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000444
Guido van Rossumadb31051994-06-23 11:42:52 +0000445class TupleComp:
Tim Peters2344fae2001-01-15 00:50:52 +0000446 """This class provides a generic function for comparing any two tuples.
447 Each instance records a list of tuple-indices (from most significant
448 to least significant), and sort direction (ascending or decending) for
449 each tuple-index. The compare functions can then be used as the function
450 argument to the system sort() function when a list of tuples need to be
451 sorted in the instances order."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000452
Tim Peters2344fae2001-01-15 00:50:52 +0000453 def __init__(self, comp_select_list):
454 self.comp_select_list = comp_select_list
Guido van Rossumadb31051994-06-23 11:42:52 +0000455
Tim Peters2344fae2001-01-15 00:50:52 +0000456 def compare (self, left, right):
457 for index, direction in self.comp_select_list:
458 l = left[index]
459 r = right[index]
460 if l < r:
461 return -direction
462 if l > r:
463 return direction
464 return 0
Guido van Rossumadb31051994-06-23 11:42:52 +0000465
Raymond Hettinger70b64fc2008-01-30 20:15:17 +0000466def CmpToKey(mycmp):
467 'Convert a cmp= function into a key= function'
468 class K(object):
469 def __init__(self, obj):
470 self.obj = obj
471 def __lt__(self, other):
472 return mycmp(self.obj, other.obj) == -1
473 return K
474
475
Guido van Rossumadb31051994-06-23 11:42:52 +0000476#**************************************************************************
Tim Peters7d016852001-10-08 06:13:19 +0000477# func_name is a triple (file:string, line:int, name:string)
Guido van Rossumadb31051994-06-23 11:42:52 +0000478
479def func_strip_path(func_name):
Fred Drake9c439102003-05-14 14:28:09 +0000480 filename, line, name = func_name
481 return os.path.basename(filename), line, name
Guido van Rossumadb31051994-06-23 11:42:52 +0000482
483def func_get_function_name(func):
Tim Peters2344fae2001-01-15 00:50:52 +0000484 return func[2]
Guido van Rossumadb31051994-06-23 11:42:52 +0000485
486def func_std_string(func_name): # match what old profile produced
Armin Rigoa871ef22006-02-08 12:53:56 +0000487 if func_name[:2] == ('~', 0):
488 # special case for built-in functions
489 name = func_name[2]
490 if name.startswith('<') and name.endswith('>'):
491 return '{%s}' % name[1:-1]
492 else:
493 return name
494 else:
495 return "%s:%d(%s)" % func_name
Guido van Rossumadb31051994-06-23 11:42:52 +0000496
497#**************************************************************************
498# The following functions combine statists for pairs functions.
499# The bulk of the processing involves correctly handling "call" lists,
Tim Peters2344fae2001-01-15 00:50:52 +0000500# such as callers and callees.
Guido van Rossumadb31051994-06-23 11:42:52 +0000501#**************************************************************************
502
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000503def add_func_stats(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000504 """Add together all the stats for two profile entries."""
505 cc, nc, tt, ct, callers = source
506 t_cc, t_nc, t_tt, t_ct, t_callers = target
Tim Peters7d016852001-10-08 06:13:19 +0000507 return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct,
Tim Peters2344fae2001-01-15 00:50:52 +0000508 add_callers(t_callers, callers))
Guido van Rossumadb31051994-06-23 11:42:52 +0000509
Guido van Rossumadb31051994-06-23 11:42:52 +0000510def add_callers(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000511 """Combine two caller lists in a single list."""
512 new_callers = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000513 for func, caller in target.items():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000514 new_callers[func] = caller
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000515 for func, caller in source.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000516 if func in new_callers:
Georg Brandle90bce72010-08-02 17:36:05 +0000517 if isinstance(caller, tuple):
518 # format used by cProfile
519 new_callers[func] = tuple([i[0] + i[1] for i in
520 zip(caller, new_callers[func])])
521 else:
522 # format used by profile
523 new_callers[func] += caller
Tim Peters2344fae2001-01-15 00:50:52 +0000524 else:
Raymond Hettingere0d49722002-06-02 18:55:56 +0000525 new_callers[func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000526 return new_callers
Guido van Rossumadb31051994-06-23 11:42:52 +0000527
Guido van Rossumadb31051994-06-23 11:42:52 +0000528def count_calls(callers):
Tim Peters2344fae2001-01-15 00:50:52 +0000529 """Sum the caller statistics to get total number of calls received."""
530 nc = 0
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000531 for calls in callers.values():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000532 nc += calls
Tim Peters2344fae2001-01-15 00:50:52 +0000533 return nc
Guido van Rossumadb31051994-06-23 11:42:52 +0000534
535#**************************************************************************
536# The following functions support printing of reports
537#**************************************************************************
538
539def f8(x):
Tim Peters7d016852001-10-08 06:13:19 +0000540 return "%8.3f" % x
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000541
542#**************************************************************************
543# Statistics browser added by ESR, April 2001
544#**************************************************************************
545
546if __name__ == '__main__':
547 import cmd
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000548 try:
549 import readline
Fred Drakee8187612001-05-11 19:21:41 +0000550 except ImportError:
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000551 pass
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000552
553 class ProfileBrowser(cmd.Cmd):
554 def __init__(self, profile=None):
Martin v. Löwis66b6e192001-07-28 14:44:03 +0000555 cmd.Cmd.__init__(self)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000556 self.prompt = "% "
Georg Brandl4009c9e2010-10-06 08:26:09 +0000557 self.stats = None
558 self.stream = sys.stdout
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000559 if profile is not None:
Georg Brandl4009c9e2010-10-06 08:26:09 +0000560 self.do_read(profile)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000561
562 def generic(self, fn, line):
563 args = line.split()
564 processed = []
565 for term in args:
566 try:
567 processed.append(int(term))
568 continue
569 except ValueError:
570 pass
571 try:
572 frac = float(term)
573 if frac > 1 or frac < 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000574 print("Fraction argument must be in [0, 1]", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000575 continue
576 processed.append(frac)
577 continue
578 except ValueError:
579 pass
580 processed.append(term)
581 if self.stats:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000582 getattr(self.stats, fn)(*processed)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000583 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000584 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000585 return 0
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000586 def generic_help(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000587 print("Arguments may be:", file=self.stream)
588 print("* An integer maximum number of entries to print.", file=self.stream)
589 print("* A decimal fractional number between 0 and 1, controlling", file=self.stream)
590 print(" what fraction of selected entries to print.", file=self.stream)
591 print("* A regular expression; only entries with function names", file=self.stream)
592 print(" that match it are printed.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000593
594 def do_add(self, line):
Georg Brandl44c58232010-08-01 19:04:55 +0000595 if self.stats:
596 self.stats.add(line)
597 else:
598 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000599 return 0
600 def help_add(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000601 print("Add profile info from given file to current statistics object.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000602
603 def do_callees(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000604 return self.generic('print_callees', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000605 def help_callees(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000606 print("Print callees statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000607 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000608
609 def do_callers(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000610 return self.generic('print_callers', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000611 def help_callers(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000612 print("Print callers statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000613 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000614
615 def do_EOF(self, line):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000616 print("", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000617 return 1
618 def help_EOF(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000619 print("Leave the profile brower.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000620
621 def do_quit(self, line):
622 return 1
623 def help_quit(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000624 print("Leave the profile brower.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000625
626 def do_read(self, line):
627 if line:
628 try:
629 self.stats = Stats(line)
Georg Brandl50da60c2008-01-06 21:38:54 +0000630 except IOError as err:
631 print(err.args[1], file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000632 return
Georg Brandl44c58232010-08-01 19:04:55 +0000633 except Exception as err:
634 print(err.__class__.__name__ + ':', err, file=self.stream)
635 return
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000636 self.prompt = line + "% "
Martin v. Löwis22adac52001-06-07 05:49:05 +0000637 elif len(self.prompt) > 2:
Georg Brandl44c58232010-08-01 19:04:55 +0000638 line = self.prompt[:-2]
639 self.do_read(line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000640 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000641 print("No statistics object is current -- cannot reload.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000642 return 0
643 def help_read(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000644 print("Read in profile data from a specified file.", file=self.stream)
Georg Brandl44c58232010-08-01 19:04:55 +0000645 print("Without argument, reload the current file.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000646
647 def do_reverse(self, line):
Georg Brandl44c58232010-08-01 19:04:55 +0000648 if self.stats:
649 self.stats.reverse_order()
650 else:
651 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000652 return 0
653 def help_reverse(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000654 print("Reverse the sort order of the profiling report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000655
656 def do_sort(self, line):
Georg Brandl44c58232010-08-01 19:04:55 +0000657 if not self.stats:
658 print("No statistics object is loaded.", file=self.stream)
659 return
Raymond Hettingere0d49722002-06-02 18:55:56 +0000660 abbrevs = self.stats.get_sort_arg_defs()
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000661 if line and not filter(lambda x,a=abbrevs: x not in a,line.split()):
Guido van Rossum68468eb2003-02-27 20:14:51 +0000662 self.stats.sort_stats(*line.split())
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000663 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000664 print("Valid sort keys (unique prefixes are accepted):", file=self.stream)
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000665 for (key, value) in Stats.sort_arg_dict_default.items():
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000666 print("%s -- %s" % (key, value[1]), file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000667 return 0
668 def help_sort(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000669 print("Sort profile data according to specified keys.", file=self.stream)
670 print("(Typing `sort' without arguments lists valid keys.)", file=self.stream)
Martin v. Löwis27c430e2001-07-30 10:21:13 +0000671 def complete_sort(self, text, *args):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000672 return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000673
674 def do_stats(self, line):
675 return self.generic('print_stats', line)
676 def help_stats(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000677 print("Print statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000678 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000679
680 def do_strip(self, line):
Georg Brandl44c58232010-08-01 19:04:55 +0000681 if self.stats:
682 self.stats.strip_dirs()
683 else:
684 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000685 def help_strip(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000686 print("Strip leading path information from filenames in the report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000687
Georg Brandl44c58232010-08-01 19:04:55 +0000688 def help_help(self):
689 print("Show help for a given command.", file=self.stream)
690
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000691 def postcmd(self, stop, line):
692 if stop:
693 return stop
694 return None
695
696 import sys
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000697 if len(sys.argv) > 1:
698 initprofile = sys.argv[1]
699 else:
700 initprofile = None
701 try:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000702 browser = ProfileBrowser(initprofile)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000703 print("Welcome to the profile statistics browser.", file=browser.stream)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000704 browser.cmdloop()
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000705 print("Goodbye.", file=browser.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000706 except KeyboardInterrupt:
707 pass
708
709# That's all, folks.