blob: 0f93ae02c95074dfd6bddff780474d11c28a654c [file] [log] [blame]
Guido van Rossum54f22ed2000-02-04 15:10:34 +00001"""Class for printing reports on profiled python code."""
2
Benjamin Peterson8d770692011-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 Peterson8d770692011-06-27 09:14:34 -05007# Copyright Disney Enterprises, Inc. All Rights Reserved.
8# Licensed to PSF under a Contributor Agreement
Benjamin Peterson0f93d3d2011-06-27 09:18:46 -05009#
Benjamin Peterson8d770692011-06-27 09:14:34 -050010# Licensed under the Apache License, Version 2.0 (the "License");
11# you may not use this file except in compliance with the License.
12# You may obtain a copy of the License at
Benjamin Peterson0f93d3d2011-06-27 09:18:46 -050013#
Benjamin Peterson8d770692011-06-27 09:14:34 -050014# http://www.apache.org/licenses/LICENSE-2.0
Benjamin Peterson0f93d3d2011-06-27 09:18:46 -050015#
Benjamin Peterson8d770692011-06-27 09:14:34 -050016# Unless required by applicable law or agreed to in writing, software
17# distributed under the License is distributed on an "AS IS" BASIS,
18# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
19# either express or implied. See the License for the specific language
20# governing permissions and limitations under the License.
Guido van Rossumadb31051994-06-23 11:42:52 +000021
22
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
Daniel Olshansky01602ae2020-01-15 17:51:54 -050028
mwidjaja863b1e42018-01-25 20:49:56 -080029from enum import Enum
Raymond Hettingerc50846a2010-04-05 18:56:31 +000030from functools import cmp_to_key
Daniel Olshansky01602ae2020-01-15 17:51:54 -050031from dataclasses import dataclass
32from typing import Dict
Guido van Rossumadb31051994-06-23 11:42:52 +000033
Daniel Olshansky01602ae2020-01-15 17:51:54 -050034__all__ = ["Stats", "SortKey", "FunctionProfile", "StatsProfile"]
mwidjaja863b1e42018-01-25 20:49:56 -080035
36class SortKey(str, Enum):
37 CALLS = 'calls', 'ncalls'
38 CUMULATIVE = 'cumulative', 'cumtime'
39 FILENAME = 'filename', 'module'
40 LINE = 'line'
41 NAME = 'name'
42 NFL = 'nfl'
43 PCALLS = 'pcalls'
44 STDNAME = 'stdname'
45 TIME = 'time', 'tottime'
46
47 def __new__(cls, *values):
Ethan Furmanae0d2a32020-09-19 11:12:57 -070048 value = values[0]
49 obj = str.__new__(cls, value)
50 obj._value_ = value
mwidjaja863b1e42018-01-25 20:49:56 -080051 for other_value in values[1:]:
52 cls._value2member_map_[other_value] = obj
53 obj._all_values = values
54 return obj
55
Skip Montanaroc62c81e2001-02-12 02:00:42 +000056
Daniel Olshansky01602ae2020-01-15 17:51:54 -050057@dataclass(unsafe_hash=True)
58class FunctionProfile:
59 ncalls: int
60 tottime: float
61 percall_tottime: float
62 cumtime: float
63 percall_cumtime: float
64 file_name: str
65 line_number: int
66
67@dataclass(unsafe_hash=True)
68class StatsProfile:
69 '''Class for keeping track of an item in inventory.'''
70 total_tt: float
71 func_profiles: Dict[str, FunctionProfile]
72
Guido van Rossumadb31051994-06-23 11:42:52 +000073class Stats:
Tim Peters2344fae2001-01-15 00:50:52 +000074 """This class is used for creating reports from data generated by the
75 Profile class. It is a "friend" of that class, and imports data either
76 by direct access to members of Profile class, or by reading in a dictionary
77 that was emitted (via marshal) from the Profile class.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000078
Tim Peters2344fae2001-01-15 00:50:52 +000079 The big change from the previous Profiler (in terms of raw functionality)
80 is that an "add()" method has been provided to combine Stats from
81 several distinct profile runs. Both the constructor and the add()
82 method now take arbitrarily many file names as arguments.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000083
Tim Peters2344fae2001-01-15 00:50:52 +000084 All the print methods now take an argument that indicates how many lines
85 to print. If the arg is a floating point number between 0 and 1.0, then
86 it is taken as a decimal percentage of the available lines to be printed
87 (e.g., .1 means print 10% of all available lines). If it is an integer,
88 it is taken to mean the number of lines of data that you wish to have
89 printed.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000090
Tim Peters2344fae2001-01-15 00:50:52 +000091 The sort_stats() method now processes some additional options (i.e., in
Matthias Bussonnier8fb1f6e2017-02-20 21:30:00 -080092 addition to the old -1, 0, 1, or 2 that are respectively interpreted as
mwidjaja863b1e42018-01-25 20:49:56 -080093 'stdname', 'calls', 'time', and 'cumulative'). It takes either an
94 arbitrary number of quoted strings or SortKey enum to select the sort
95 order.
Matthias Bussonnier8fb1f6e2017-02-20 21:30:00 -080096
mwidjaja863b1e42018-01-25 20:49:56 -080097 For example sort_stats('time', 'name') or sort_stats(SortKey.TIME,
98 SortKey.NAME) sorts on the major key of 'internal function time', and on
99 the minor key of 'the name of the function'. Look at the two tables in
100 sort_stats() and get_sort_arg_defs(self) for more examples.
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000101
Georg Brandlb1a97af2010-08-02 12:06:18 +0000102 All methods return self, so you can string together commands like:
Tim Peters2344fae2001-01-15 00:50:52 +0000103 Stats('foo', 'goo').strip_dirs().sort_stats('calls').\
104 print_stats(5).print_callers(5)
105 """
106
Georg Brandl7837a962009-09-02 20:33:30 +0000107 def __init__(self, *args, stream=None):
108 self.stream = stream or sys.stdout
Tim Peters2344fae2001-01-15 00:50:52 +0000109 if not len(args):
110 arg = None
111 else:
112 arg = args[0]
113 args = args[1:]
114 self.init(arg)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000115 self.add(*args)
Tim Peters2344fae2001-01-15 00:50:52 +0000116
117 def init(self, arg):
118 self.all_callees = None # calc only if needed
119 self.files = []
120 self.fcn_list = None
121 self.total_tt = 0
122 self.total_calls = 0
123 self.prim_calls = 0
124 self.max_name_len = 0
Georg Brandleb7e5692010-10-22 06:29:21 +0000125 self.top_level = set()
Tim Peters2344fae2001-01-15 00:50:52 +0000126 self.stats = {}
127 self.sort_arg_dict = {}
128 self.load_stats(arg)
Tim Peters2344fae2001-01-15 00:50:52 +0000129 try:
130 self.get_top_level_stats()
Georg Brandl9a8439d2010-10-22 06:35:59 +0000131 except Exception:
132 print("Invalid timing data %s" %
133 (self.files[-1] if self.files else ''), file=self.stream)
134 raise
Guido van Rossumadb31051994-06-23 11:42:52 +0000135
Tim Peters2344fae2001-01-15 00:50:52 +0000136 def load_stats(self, arg):
Georg Brandl83938432010-10-22 06:28:01 +0000137 if arg is None:
138 self.stats = {}
139 return
Guido van Rossum3172c5d2007-10-16 18:12:55 +0000140 elif isinstance(arg, str):
Giampaolo Rodola'2f50aaf2013-02-12 02:04:27 +0100141 with open(arg, 'rb') as f:
142 self.stats = marshal.load(f)
Tim Peters2344fae2001-01-15 00:50:52 +0000143 try:
144 file_stats = os.stat(arg)
Raymond Hettinger32200ae2002-06-01 19:51:15 +0000145 arg = time.ctime(file_stats.st_mtime) + " " + arg
Tim Peters2344fae2001-01-15 00:50:52 +0000146 except: # in case this is not unix
147 pass
Georg Brandl83938432010-10-22 06:28:01 +0000148 self.files = [arg]
Tim Peters2344fae2001-01-15 00:50:52 +0000149 elif hasattr(arg, 'create_stats'):
150 arg.create_stats()
151 self.stats = arg.stats
152 arg.stats = {}
153 if not self.stats:
Georg Brandl83938432010-10-22 06:28:01 +0000154 raise TypeError("Cannot create or construct a %r object from %r"
Collin Winterce36ad82007-08-30 01:19:48 +0000155 % (self.__class__, arg))
Tim Peters2344fae2001-01-15 00:50:52 +0000156 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000157
Tim Peters2344fae2001-01-15 00:50:52 +0000158 def get_top_level_stats(self):
Tim Peters7d016852001-10-08 06:13:19 +0000159 for func, (cc, nc, tt, ct, callers) in self.stats.items():
160 self.total_calls += nc
161 self.prim_calls += cc
162 self.total_tt += tt
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000163 if ("jprofile", 0, "profiler") in callers:
Georg Brandleb7e5692010-10-22 06:29:21 +0000164 self.top_level.add(func)
Tim Peters2344fae2001-01-15 00:50:52 +0000165 if len(func_std_string(func)) > self.max_name_len:
166 self.max_name_len = len(func_std_string(func))
Guido van Rossumadb31051994-06-23 11:42:52 +0000167
Tim Peters2344fae2001-01-15 00:50:52 +0000168 def add(self, *arg_list):
Georg Brandl83938432010-10-22 06:28:01 +0000169 if not arg_list:
170 return self
171 for item in reversed(arg_list):
172 if type(self) != type(item):
173 item = Stats(item)
174 self.files += item.files
175 self.total_calls += item.total_calls
176 self.prim_calls += item.prim_calls
177 self.total_tt += item.total_tt
178 for func in item.top_level:
Georg Brandleb7e5692010-10-22 06:29:21 +0000179 self.top_level.add(func)
Guido van Rossumadb31051994-06-23 11:42:52 +0000180
Georg Brandl83938432010-10-22 06:28:01 +0000181 if self.max_name_len < item.max_name_len:
182 self.max_name_len = item.max_name_len
Guido van Rossumadb31051994-06-23 11:42:52 +0000183
Georg Brandl83938432010-10-22 06:28:01 +0000184 self.fcn_list = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000185
Georg Brandl83938432010-10-22 06:28:01 +0000186 for func, stat in item.stats.items():
187 if func in self.stats:
188 old_func_stat = self.stats[func]
189 else:
190 old_func_stat = (0, 0, 0, 0, {},)
191 self.stats[func] = add_func_stats(old_func_stat, stat)
Tim Peters2344fae2001-01-15 00:50:52 +0000192 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000193
Fred Drake9c439102003-05-14 14:28:09 +0000194 def dump_stats(self, filename):
195 """Write the profile data to a file we know how to load back."""
Giampaolo Rodola'2f50aaf2013-02-12 02:04:27 +0100196 with open(filename, 'wb') as f:
Fred Drake9c439102003-05-14 14:28:09 +0000197 marshal.dump(self.stats, f)
Fred Drake9c439102003-05-14 14:28:09 +0000198
Tim Peters2344fae2001-01-15 00:50:52 +0000199 # list the tuple indices and directions for sorting,
200 # along with some printable description
Tim Peters7d016852001-10-08 06:13:19 +0000201 sort_arg_dict_default = {
202 "calls" : (((1,-1), ), "call count"),
Andrew Svetlovc3e5b102012-10-07 19:18:39 +0300203 "ncalls" : (((1,-1), ), "call count"),
204 "cumtime" : (((3,-1), ), "cumulative time"),
Tim Peters7d016852001-10-08 06:13:19 +0000205 "cumulative": (((3,-1), ), "cumulative time"),
Andrew Svetlovc3e5b102012-10-07 19:18:39 +0300206 "filename" : (((4, 1), ), "file name"),
Tim Peters7d016852001-10-08 06:13:19 +0000207 "line" : (((5, 1), ), "line number"),
208 "module" : (((4, 1), ), "file name"),
209 "name" : (((6, 1), ), "function name"),
210 "nfl" : (((6, 1),(4, 1),(5, 1),), "name/file/line"),
Andrew Svetlov2ef45842012-10-07 18:58:42 +0300211 "pcalls" : (((0,-1), ), "primitive call count"),
Tim Peters7d016852001-10-08 06:13:19 +0000212 "stdname" : (((7, 1), ), "standard name"),
213 "time" : (((2,-1), ), "internal time"),
Andrew Svetlovc3e5b102012-10-07 19:18:39 +0300214 "tottime" : (((2,-1), ), "internal time"),
Tim Peters2344fae2001-01-15 00:50:52 +0000215 }
Guido van Rossumadb31051994-06-23 11:42:52 +0000216
Tim Peters2344fae2001-01-15 00:50:52 +0000217 def get_sort_arg_defs(self):
218 """Expand all abbreviations that are unique."""
219 if not self.sort_arg_dict:
220 self.sort_arg_dict = dict = {}
Tim Peters2344fae2001-01-15 00:50:52 +0000221 bad_list = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000222 for word, tup in self.sort_arg_dict_default.items():
Tim Peters2344fae2001-01-15 00:50:52 +0000223 fragment = word
224 while fragment:
225 if not fragment:
226 break
Raymond Hettinger54f02222002-06-01 14:18:47 +0000227 if fragment in dict:
Tim Peters2344fae2001-01-15 00:50:52 +0000228 bad_list[fragment] = 0
229 break
Raymond Hettingere0d49722002-06-02 18:55:56 +0000230 dict[fragment] = tup
Tim Peters2344fae2001-01-15 00:50:52 +0000231 fragment = fragment[:-1]
Raymond Hettingere0d49722002-06-02 18:55:56 +0000232 for word in bad_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000233 del dict[word]
234 return self.sort_arg_dict
Guido van Rossumadb31051994-06-23 11:42:52 +0000235
Tim Peters2344fae2001-01-15 00:50:52 +0000236 def sort_stats(self, *field):
237 if not field:
238 self.fcn_list = 0
239 return self
Georg Brandlb1a97af2010-08-02 12:06:18 +0000240 if len(field) == 1 and isinstance(field[0], int):
Tim Peters2344fae2001-01-15 00:50:52 +0000241 # Be compatible with old profiler
Tim Peters7d016852001-10-08 06:13:19 +0000242 field = [ {-1: "stdname",
Georg Brandlb1a97af2010-08-02 12:06:18 +0000243 0: "calls",
244 1: "time",
245 2: "cumulative"}[field[0]] ]
mwidjaja863b1e42018-01-25 20:49:56 -0800246 elif len(field) >= 2:
247 for arg in field[1:]:
248 if type(arg) != type(field[0]):
249 raise TypeError("Can't have mixed argument type")
Tim Peters2344fae2001-01-15 00:50:52 +0000250
251 sort_arg_defs = self.get_sort_arg_defs()
mwidjaja863b1e42018-01-25 20:49:56 -0800252
Tim Peters2344fae2001-01-15 00:50:52 +0000253 sort_tuple = ()
254 self.sort_type = ""
255 connector = ""
256 for word in field:
mwidjaja863b1e42018-01-25 20:49:56 -0800257 if isinstance(word, SortKey):
258 word = word.value
Tim Peters2344fae2001-01-15 00:50:52 +0000259 sort_tuple = sort_tuple + sort_arg_defs[word][0]
Tim Peters7d016852001-10-08 06:13:19 +0000260 self.sort_type += connector + sort_arg_defs[word][1]
Tim Peters2344fae2001-01-15 00:50:52 +0000261 connector = ", "
262
263 stats_list = []
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000264 for func, (cc, nc, tt, ct, callers) in self.stats.items():
Tim Peters7d016852001-10-08 06:13:19 +0000265 stats_list.append((cc, nc, tt, ct) + func +
266 (func_std_string(func), func))
Tim Peters2344fae2001-01-15 00:50:52 +0000267
Raymond Hettingerc50846a2010-04-05 18:56:31 +0000268 stats_list.sort(key=cmp_to_key(TupleComp(sort_tuple).compare))
Tim Peters2344fae2001-01-15 00:50:52 +0000269
270 self.fcn_list = fcn_list = []
271 for tuple in stats_list:
272 fcn_list.append(tuple[-1])
273 return self
274
Tim Peters2344fae2001-01-15 00:50:52 +0000275 def reverse_order(self):
Tim Peters7d016852001-10-08 06:13:19 +0000276 if self.fcn_list:
277 self.fcn_list.reverse()
Tim Peters2344fae2001-01-15 00:50:52 +0000278 return self
279
280 def strip_dirs(self):
281 oldstats = self.stats
282 self.stats = newstats = {}
283 max_name_len = 0
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000284 for func, (cc, nc, tt, ct, callers) in oldstats.items():
Tim Peters2344fae2001-01-15 00:50:52 +0000285 newfunc = func_strip_path(func)
286 if len(func_std_string(newfunc)) > max_name_len:
287 max_name_len = len(func_std_string(newfunc))
288 newcallers = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000289 for func2, caller in callers.items():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000290 newcallers[func_strip_path(func2)] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000291
Raymond Hettinger54f02222002-06-01 14:18:47 +0000292 if newfunc in newstats:
Tim Peters7d016852001-10-08 06:13:19 +0000293 newstats[newfunc] = add_func_stats(
294 newstats[newfunc],
295 (cc, nc, tt, ct, newcallers))
Tim Peters2344fae2001-01-15 00:50:52 +0000296 else:
297 newstats[newfunc] = (cc, nc, tt, ct, newcallers)
298 old_top = self.top_level
Georg Brandleb7e5692010-10-22 06:29:21 +0000299 self.top_level = new_top = set()
Raymond Hettingere0d49722002-06-02 18:55:56 +0000300 for func in old_top:
Georg Brandleb7e5692010-10-22 06:29:21 +0000301 new_top.add(func_strip_path(func))
Tim Peters2344fae2001-01-15 00:50:52 +0000302
303 self.max_name_len = max_name_len
304
305 self.fcn_list = None
306 self.all_callees = None
307 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000308
Tim Peters2344fae2001-01-15 00:50:52 +0000309 def calc_callees(self):
Georg Brandl9a8439d2010-10-22 06:35:59 +0000310 if self.all_callees:
311 return
Tim Peters2344fae2001-01-15 00:50:52 +0000312 self.all_callees = all_callees = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000313 for func, (cc, nc, tt, ct, callers) in self.stats.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000314 if not func in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000315 all_callees[func] = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000316 for func2, caller in callers.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000317 if not func2 in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000318 all_callees[func2] = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000319 all_callees[func2][func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000320 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000321
Tim Peters2344fae2001-01-15 00:50:52 +0000322 #******************************************************************
323 # The following functions support actual printing of reports
324 #******************************************************************
Guido van Rossumadb31051994-06-23 11:42:52 +0000325
Tim Peters2344fae2001-01-15 00:50:52 +0000326 # Optional "amount" is either a line count, or a percentage of lines.
Guido van Rossumadb31051994-06-23 11:42:52 +0000327
Tim Peters2344fae2001-01-15 00:50:52 +0000328 def eval_print_amount(self, sel, list, msg):
329 new_list = list
Georg Brandlb1a97af2010-08-02 12:06:18 +0000330 if isinstance(sel, str):
331 try:
332 rex = re.compile(sel)
333 except re.error:
334 msg += " <Invalid regular expression %r>\n" % sel
335 return new_list, msg
Tim Peters2344fae2001-01-15 00:50:52 +0000336 new_list = []
337 for func in list:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000338 if rex.search(func_std_string(func)):
Tim Peters2344fae2001-01-15 00:50:52 +0000339 new_list.append(func)
340 else:
341 count = len(list)
Georg Brandlb1a97af2010-08-02 12:06:18 +0000342 if isinstance(sel, float) and 0.0 <= sel < 1.0:
Tim Peters7d016852001-10-08 06:13:19 +0000343 count = int(count * sel + .5)
Tim Peters2344fae2001-01-15 00:50:52 +0000344 new_list = list[:count]
Georg Brandlb1a97af2010-08-02 12:06:18 +0000345 elif isinstance(sel, int) and 0 <= sel < count:
Tim Peters2344fae2001-01-15 00:50:52 +0000346 count = sel
347 new_list = list[:count]
348 if len(list) != len(new_list):
Georg Brandlb1a97af2010-08-02 12:06:18 +0000349 msg += " List reduced from %r to %r due to restriction <%r>\n" % (
350 len(list), len(new_list), sel)
Guido van Rossumadb31051994-06-23 11:42:52 +0000351
Tim Peters2344fae2001-01-15 00:50:52 +0000352 return new_list, msg
Guido van Rossumadb31051994-06-23 11:42:52 +0000353
Daniel Olshansky01602ae2020-01-15 17:51:54 -0500354 def get_stats_profile(self):
355 """This method returns an instance of StatsProfile, which contains a mapping
356 of function names to instances of FunctionProfile. Each FunctionProfile
357 instance holds information related to the function's profile such as how
358 long the function took to run, how many times it was called, etc...
359 """
360 func_list = self.fcn_list[:] if self.fcn_list else list(self.stats.keys())
361 if not func_list:
362 return StatsProfile(0, {})
363
364 total_tt = float(f8(self.total_tt))
365 func_profiles = {}
366 stats_profile = StatsProfile(total_tt, func_profiles)
367
368 for func in func_list:
369 cc, nc, tt, ct, callers = self.stats[func]
370 file_name, line_number, func_name = func
371 ncalls = str(nc) if nc == cc else (str(nc) + '/' + str(cc))
372 tottime = float(f8(tt))
373 percall_tottime = -1 if nc == 0 else float(f8(tt/nc))
374 cumtime = float(f8(ct))
375 percall_cumtime = -1 if cc == 0 else float(f8(ct/cc))
376 func_profile = FunctionProfile(
377 ncalls,
378 tottime, # time spent in this function alone
379 percall_tottime,
380 cumtime, # time spent in the function plus all functions that this function called,
381 percall_cumtime,
382 file_name,
383 line_number
384 )
385 func_profiles[func_name] = func_profile
386
387 return stats_profile
388
Tim Peters2344fae2001-01-15 00:50:52 +0000389 def get_print_list(self, sel_list):
390 width = self.max_name_len
391 if self.fcn_list:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000392 stat_list = self.fcn_list[:]
Tim Peters2344fae2001-01-15 00:50:52 +0000393 msg = " Ordered by: " + self.sort_type + '\n'
394 else:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000395 stat_list = list(self.stats.keys())
Tim Peters2344fae2001-01-15 00:50:52 +0000396 msg = " Random listing order was used\n"
397
398 for selection in sel_list:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000399 stat_list, msg = self.eval_print_amount(selection, stat_list, msg)
Tim Peters2344fae2001-01-15 00:50:52 +0000400
Georg Brandlb1a97af2010-08-02 12:06:18 +0000401 count = len(stat_list)
Tim Peters2344fae2001-01-15 00:50:52 +0000402
Georg Brandlb1a97af2010-08-02 12:06:18 +0000403 if not stat_list:
404 return 0, stat_list
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000405 print(msg, file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000406 if count < len(self.stats):
407 width = 0
Georg Brandlb1a97af2010-08-02 12:06:18 +0000408 for func in stat_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000409 if len(func_std_string(func)) > width:
410 width = len(func_std_string(func))
Georg Brandlb1a97af2010-08-02 12:06:18 +0000411 return width+2, stat_list
Tim Peters2344fae2001-01-15 00:50:52 +0000412
413 def print_stats(self, *amount):
414 for filename in self.files:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000415 print(filename, file=self.stream)
Georg Brandl9a8439d2010-10-22 06:35:59 +0000416 if self.files:
417 print(file=self.stream)
Tim Peters7d016852001-10-08 06:13:19 +0000418 indent = ' ' * 8
Raymond Hettingere0d49722002-06-02 18:55:56 +0000419 for func in self.top_level:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000420 print(indent, func_get_function_name(func), file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000421
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000422 print(indent, self.total_calls, "function calls", end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000423 if self.total_calls != self.prim_calls:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000424 print("(%d primitive calls)" % self.prim_calls, end=' ', file=self.stream)
Senthil Kumaran5e703cf2010-11-20 17:02:50 +0000425 print("in %.3f seconds" % self.total_tt, file=self.stream)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000426 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000427 width, list = self.get_print_list(amount)
428 if list:
429 self.print_title()
430 for func in list:
431 self.print_line(func)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000432 print(file=self.stream)
433 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000434 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000435
Tim Peters2344fae2001-01-15 00:50:52 +0000436 def print_callees(self, *amount):
437 width, list = self.get_print_list(amount)
438 if list:
439 self.calc_callees()
440
441 self.print_call_heading(width, "called...")
442 for func in list:
Raymond Hettinger54f02222002-06-01 14:18:47 +0000443 if func in self.all_callees:
Tim Peters7d016852001-10-08 06:13:19 +0000444 self.print_call_line(width, func, self.all_callees[func])
Tim Peters2344fae2001-01-15 00:50:52 +0000445 else:
446 self.print_call_line(width, func, {})
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000447 print(file=self.stream)
448 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000449 return self
450
451 def print_callers(self, *amount):
452 width, list = self.get_print_list(amount)
453 if list:
454 self.print_call_heading(width, "was called by...")
455 for func in list:
456 cc, nc, tt, ct, callers = self.stats[func]
Armin Rigoa871ef22006-02-08 12:53:56 +0000457 self.print_call_line(width, func, callers, "<-")
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000458 print(file=self.stream)
459 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000460 return self
461
462 def print_call_heading(self, name_size, column_title):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000463 print("Function ".ljust(name_size) + column_title, file=self.stream)
Armin Rigoa871ef22006-02-08 12:53:56 +0000464 # print sub-header only if we have new-style callers
465 subheader = False
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000466 for cc, nc, tt, ct, callers in self.stats.values():
Armin Rigoa871ef22006-02-08 12:53:56 +0000467 if callers:
Georg Brandla18af4e2007-04-21 15:47:16 +0000468 value = next(iter(callers.values()))
Armin Rigoa871ef22006-02-08 12:53:56 +0000469 subheader = isinstance(value, tuple)
470 break
471 if subheader:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000472 print(" "*name_size + " ncalls tottime cumtime", file=self.stream)
Guido van Rossumadb31051994-06-23 11:42:52 +0000473
Armin Rigoa871ef22006-02-08 12:53:56 +0000474 def print_call_line(self, name_size, source, call_dict, arrow="->"):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000475 print(func_std_string(source).ljust(name_size) + arrow, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000476 if not call_dict:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000477 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000478 return
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000479 clist = sorted(call_dict.keys())
Tim Peters2344fae2001-01-15 00:50:52 +0000480 indent = ""
481 for func in clist:
482 name = func_std_string(func)
Armin Rigoa871ef22006-02-08 12:53:56 +0000483 value = call_dict[func]
484 if isinstance(value, tuple):
485 nc, cc, tt, ct = value
486 if nc != cc:
487 substats = '%d/%d' % (nc, cc)
488 else:
489 substats = '%d' % (nc,)
490 substats = '%s %s %s %s' % (substats.rjust(7+2*len(indent)),
491 f8(tt), f8(ct), name)
492 left_width = name_size + 1
493 else:
494 substats = '%s(%r) %s' % (name, value, f8(self.stats[func][3]))
495 left_width = name_size + 3
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000496 print(indent*left_width + substats, file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000497 indent = " "
498
Tim Peters2344fae2001-01-15 00:50:52 +0000499 def print_title(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000500 print(' ncalls tottime percall cumtime percall', end=' ', file=self.stream)
501 print('filename:lineno(function)', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000502
Georg Brandl9a8439d2010-10-22 06:35:59 +0000503 def print_line(self, func): # hack: should print percentages
Tim Peters2344fae2001-01-15 00:50:52 +0000504 cc, nc, tt, ct, callers = self.stats[func]
Tim Peters7d016852001-10-08 06:13:19 +0000505 c = str(nc)
Tim Peters2344fae2001-01-15 00:50:52 +0000506 if nc != cc:
Tim Peters7d016852001-10-08 06:13:19 +0000507 c = c + '/' + str(cc)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000508 print(c.rjust(9), end=' ', file=self.stream)
509 print(f8(tt), end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000510 if nc == 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000511 print(' '*8, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000512 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000513 print(f8(tt/nc), end=' ', file=self.stream)
514 print(f8(ct), end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000515 if cc == 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000516 print(' '*8, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000517 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000518 print(f8(ct/cc), end=' ', file=self.stream)
519 print(func_std_string(func), file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000520
Guido van Rossumadb31051994-06-23 11:42:52 +0000521class TupleComp:
Tim Peters2344fae2001-01-15 00:50:52 +0000522 """This class provides a generic function for comparing any two tuples.
523 Each instance records a list of tuple-indices (from most significant
524 to least significant), and sort direction (ascending or decending) for
525 each tuple-index. The compare functions can then be used as the function
526 argument to the system sort() function when a list of tuples need to be
527 sorted in the instances order."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000528
Tim Peters2344fae2001-01-15 00:50:52 +0000529 def __init__(self, comp_select_list):
530 self.comp_select_list = comp_select_list
Guido van Rossumadb31051994-06-23 11:42:52 +0000531
Tim Peters2344fae2001-01-15 00:50:52 +0000532 def compare (self, left, right):
533 for index, direction in self.comp_select_list:
534 l = left[index]
535 r = right[index]
536 if l < r:
537 return -direction
538 if l > r:
539 return direction
540 return 0
Guido van Rossumadb31051994-06-23 11:42:52 +0000541
Raymond Hettinger70b64fc2008-01-30 20:15:17 +0000542
Guido van Rossumadb31051994-06-23 11:42:52 +0000543#**************************************************************************
Tim Peters7d016852001-10-08 06:13:19 +0000544# func_name is a triple (file:string, line:int, name:string)
Guido van Rossumadb31051994-06-23 11:42:52 +0000545
546def func_strip_path(func_name):
Fred Drake9c439102003-05-14 14:28:09 +0000547 filename, line, name = func_name
548 return os.path.basename(filename), line, name
Guido van Rossumadb31051994-06-23 11:42:52 +0000549
550def func_get_function_name(func):
Tim Peters2344fae2001-01-15 00:50:52 +0000551 return func[2]
Guido van Rossumadb31051994-06-23 11:42:52 +0000552
553def func_std_string(func_name): # match what old profile produced
Armin Rigoa871ef22006-02-08 12:53:56 +0000554 if func_name[:2] == ('~', 0):
555 # special case for built-in functions
556 name = func_name[2]
557 if name.startswith('<') and name.endswith('>'):
558 return '{%s}' % name[1:-1]
559 else:
560 return name
561 else:
562 return "%s:%d(%s)" % func_name
Guido van Rossumadb31051994-06-23 11:42:52 +0000563
564#**************************************************************************
penguindustin96466302019-05-06 14:57:17 -0400565# The following functions combine statistics for pairs functions.
Guido van Rossumadb31051994-06-23 11:42:52 +0000566# The bulk of the processing involves correctly handling "call" lists,
Tim Peters2344fae2001-01-15 00:50:52 +0000567# such as callers and callees.
Guido van Rossumadb31051994-06-23 11:42:52 +0000568#**************************************************************************
569
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000570def add_func_stats(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000571 """Add together all the stats for two profile entries."""
572 cc, nc, tt, ct, callers = source
573 t_cc, t_nc, t_tt, t_ct, t_callers = target
Tim Peters7d016852001-10-08 06:13:19 +0000574 return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct,
Tim Peters2344fae2001-01-15 00:50:52 +0000575 add_callers(t_callers, callers))
Guido van Rossumadb31051994-06-23 11:42:52 +0000576
Guido van Rossumadb31051994-06-23 11:42:52 +0000577def add_callers(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000578 """Combine two caller lists in a single list."""
579 new_callers = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000580 for func, caller in target.items():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000581 new_callers[func] = caller
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000582 for func, caller in source.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000583 if func in new_callers:
Georg Brandl2d3c4e72010-08-02 17:24:49 +0000584 if isinstance(caller, tuple):
585 # format used by cProfile
Serhiy Storchaka3f2e6f12018-02-26 16:50:11 +0200586 new_callers[func] = tuple(i + j for i, j in zip(caller, new_callers[func]))
Georg Brandl2d3c4e72010-08-02 17:24:49 +0000587 else:
588 # format used by profile
589 new_callers[func] += caller
Tim Peters2344fae2001-01-15 00:50:52 +0000590 else:
Raymond Hettingere0d49722002-06-02 18:55:56 +0000591 new_callers[func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000592 return new_callers
Guido van Rossumadb31051994-06-23 11:42:52 +0000593
Guido van Rossumadb31051994-06-23 11:42:52 +0000594def count_calls(callers):
Tim Peters2344fae2001-01-15 00:50:52 +0000595 """Sum the caller statistics to get total number of calls received."""
596 nc = 0
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000597 for calls in callers.values():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000598 nc += calls
Tim Peters2344fae2001-01-15 00:50:52 +0000599 return nc
Guido van Rossumadb31051994-06-23 11:42:52 +0000600
601#**************************************************************************
602# The following functions support printing of reports
603#**************************************************************************
604
605def f8(x):
Tim Peters7d016852001-10-08 06:13:19 +0000606 return "%8.3f" % x
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000607
608#**************************************************************************
609# Statistics browser added by ESR, April 2001
610#**************************************************************************
611
612if __name__ == '__main__':
613 import cmd
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000614 try:
615 import readline
Brett Cannoncd171c82013-07-04 17:43:24 -0400616 except ImportError:
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000617 pass
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000618
619 class ProfileBrowser(cmd.Cmd):
620 def __init__(self, profile=None):
Martin v. Löwis66b6e192001-07-28 14:44:03 +0000621 cmd.Cmd.__init__(self)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000622 self.prompt = "% "
Georg Brandlb1a97af2010-08-02 12:06:18 +0000623 self.stats = None
624 self.stream = sys.stdout
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000625 if profile is not None:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000626 self.do_read(profile)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000627
628 def generic(self, fn, line):
629 args = line.split()
630 processed = []
631 for term in args:
632 try:
633 processed.append(int(term))
634 continue
635 except ValueError:
636 pass
637 try:
638 frac = float(term)
639 if frac > 1 or frac < 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000640 print("Fraction argument must be in [0, 1]", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000641 continue
642 processed.append(frac)
643 continue
644 except ValueError:
645 pass
646 processed.append(term)
647 if self.stats:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000648 getattr(self.stats, fn)(*processed)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000649 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000650 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000651 return 0
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000652 def generic_help(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000653 print("Arguments may be:", file=self.stream)
654 print("* An integer maximum number of entries to print.", file=self.stream)
655 print("* A decimal fractional number between 0 and 1, controlling", file=self.stream)
656 print(" what fraction of selected entries to print.", file=self.stream)
657 print("* A regular expression; only entries with function names", file=self.stream)
658 print(" that match it are printed.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000659
660 def do_add(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000661 if self.stats:
Stefan Krahe12a68b2016-08-02 22:30:24 +0200662 try:
663 self.stats.add(line)
Serhiy Storchaka55fe1ae2017-04-16 10:46:38 +0300664 except OSError as e:
Stefan Krahe12a68b2016-08-02 22:30:24 +0200665 print("Failed to load statistics for %s: %s" % (line, e), file=self.stream)
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000666 else:
667 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000668 return 0
669 def help_add(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000670 print("Add profile info from given file to current statistics object.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000671
672 def do_callees(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000673 return self.generic('print_callees', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000674 def help_callees(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000675 print("Print callees statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000676 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000677
678 def do_callers(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000679 return self.generic('print_callers', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000680 def help_callers(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000681 print("Print callers statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000682 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000683
684 def do_EOF(self, line):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000685 print("", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000686 return 1
687 def help_EOF(self):
Xtreak0d702272019-06-03 04:42:33 +0530688 print("Leave the profile browser.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000689
690 def do_quit(self, line):
691 return 1
692 def help_quit(self):
Xtreak0d702272019-06-03 04:42:33 +0530693 print("Leave the profile browser.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000694
695 def do_read(self, line):
696 if line:
697 try:
698 self.stats = Stats(line)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200699 except OSError as err:
Georg Brandl50da60c2008-01-06 21:38:54 +0000700 print(err.args[1], file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000701 return
Georg Brandlf02e7362010-08-01 07:57:47 +0000702 except Exception as err:
703 print(err.__class__.__name__ + ':', err, file=self.stream)
704 return
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000705 self.prompt = line + "% "
Martin v. Löwis22adac52001-06-07 05:49:05 +0000706 elif len(self.prompt) > 2:
Georg Brandlf02e7362010-08-01 07:57:47 +0000707 line = self.prompt[:-2]
708 self.do_read(line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000709 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000710 print("No statistics object is current -- cannot reload.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000711 return 0
712 def help_read(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000713 print("Read in profile data from a specified file.", file=self.stream)
Georg Brandlf02e7362010-08-01 07:57:47 +0000714 print("Without argument, reload the current file.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000715
716 def do_reverse(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000717 if self.stats:
718 self.stats.reverse_order()
719 else:
720 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000721 return 0
722 def help_reverse(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000723 print("Reverse the sort order of the profiling report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000724
725 def do_sort(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000726 if not self.stats:
727 print("No statistics object is loaded.", file=self.stream)
728 return
Raymond Hettingere0d49722002-06-02 18:55:56 +0000729 abbrevs = self.stats.get_sort_arg_defs()
Andrew M. Kuchling0a628232010-04-02 17:02:57 +0000730 if line and all((x in abbrevs) for x in line.split()):
Guido van Rossum68468eb2003-02-27 20:14:51 +0000731 self.stats.sort_stats(*line.split())
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000732 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000733 print("Valid sort keys (unique prefixes are accepted):", file=self.stream)
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000734 for (key, value) in Stats.sort_arg_dict_default.items():
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000735 print("%s -- %s" % (key, value[1]), file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000736 return 0
737 def help_sort(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000738 print("Sort profile data according to specified keys.", file=self.stream)
739 print("(Typing `sort' without arguments lists valid keys.)", file=self.stream)
Martin v. Löwis27c430e2001-07-30 10:21:13 +0000740 def complete_sort(self, text, *args):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000741 return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000742
743 def do_stats(self, line):
744 return self.generic('print_stats', line)
745 def help_stats(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000746 print("Print statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000747 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000748
749 def do_strip(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000750 if self.stats:
751 self.stats.strip_dirs()
752 else:
753 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000754 def help_strip(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000755 print("Strip leading path information from filenames in the report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000756
Georg Brandlf02e7362010-08-01 07:57:47 +0000757 def help_help(self):
758 print("Show help for a given command.", file=self.stream)
759
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000760 def postcmd(self, stop, line):
761 if stop:
762 return stop
763 return None
764
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000765 if len(sys.argv) > 1:
766 initprofile = sys.argv[1]
767 else:
768 initprofile = None
769 try:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000770 browser = ProfileBrowser(initprofile)
Antoine Pitrou9d8c1862012-03-14 17:47:11 +0100771 for profile in sys.argv[2:]:
772 browser.do_add(profile)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000773 print("Welcome to the profile statistics browser.", file=browser.stream)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000774 browser.cmdloop()
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000775 print("Goodbye.", file=browser.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000776 except KeyboardInterrupt:
777 pass
778
779# That's all, folks.