xref: /aosp_15_r20/prebuilts/build-tools/common/py3-stdlib/pstats.py (revision cda5da8d549138a6648c5ee6d7a49cf8f4a657be)
1*cda5da8dSAndroid Build Coastguard Worker"""Class for printing reports on profiled python code."""
2*cda5da8dSAndroid Build Coastguard Worker
3*cda5da8dSAndroid Build Coastguard Worker# Written by James Roskind
4*cda5da8dSAndroid Build Coastguard Worker# Based on prior profile module by Sjoerd Mullender...
5*cda5da8dSAndroid Build Coastguard Worker#   which was hacked somewhat by: Guido van Rossum
6*cda5da8dSAndroid Build Coastguard Worker
7*cda5da8dSAndroid Build Coastguard Worker# Copyright Disney Enterprises, Inc.  All Rights Reserved.
8*cda5da8dSAndroid Build Coastguard Worker# Licensed to PSF under a Contributor Agreement
9*cda5da8dSAndroid Build Coastguard Worker#
10*cda5da8dSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
11*cda5da8dSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
12*cda5da8dSAndroid Build Coastguard Worker# You may obtain a copy of the License at
13*cda5da8dSAndroid Build Coastguard Worker#
14*cda5da8dSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0
15*cda5da8dSAndroid Build Coastguard Worker#
16*cda5da8dSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
17*cda5da8dSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
18*cda5da8dSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
19*cda5da8dSAndroid Build Coastguard Worker# either express or implied.  See the License for the specific language
20*cda5da8dSAndroid Build Coastguard Worker# governing permissions and limitations under the License.
21*cda5da8dSAndroid Build Coastguard Worker
22*cda5da8dSAndroid Build Coastguard Worker
23*cda5da8dSAndroid Build Coastguard Workerimport sys
24*cda5da8dSAndroid Build Coastguard Workerimport os
25*cda5da8dSAndroid Build Coastguard Workerimport time
26*cda5da8dSAndroid Build Coastguard Workerimport marshal
27*cda5da8dSAndroid Build Coastguard Workerimport re
28*cda5da8dSAndroid Build Coastguard Worker
29*cda5da8dSAndroid Build Coastguard Workerfrom enum import StrEnum, _simple_enum
30*cda5da8dSAndroid Build Coastguard Workerfrom functools import cmp_to_key
31*cda5da8dSAndroid Build Coastguard Workerfrom dataclasses import dataclass
32*cda5da8dSAndroid Build Coastguard Workerfrom typing import Dict
33*cda5da8dSAndroid Build Coastguard Worker
34*cda5da8dSAndroid Build Coastguard Worker__all__ = ["Stats", "SortKey", "FunctionProfile", "StatsProfile"]
35*cda5da8dSAndroid Build Coastguard Worker
36*cda5da8dSAndroid Build Coastguard Worker@_simple_enum(StrEnum)
37*cda5da8dSAndroid Build Coastguard Workerclass SortKey:
38*cda5da8dSAndroid Build Coastguard Worker    CALLS = 'calls', 'ncalls'
39*cda5da8dSAndroid Build Coastguard Worker    CUMULATIVE = 'cumulative', 'cumtime'
40*cda5da8dSAndroid Build Coastguard Worker    FILENAME = 'filename', 'module'
41*cda5da8dSAndroid Build Coastguard Worker    LINE = 'line'
42*cda5da8dSAndroid Build Coastguard Worker    NAME = 'name'
43*cda5da8dSAndroid Build Coastguard Worker    NFL = 'nfl'
44*cda5da8dSAndroid Build Coastguard Worker    PCALLS = 'pcalls'
45*cda5da8dSAndroid Build Coastguard Worker    STDNAME = 'stdname'
46*cda5da8dSAndroid Build Coastguard Worker    TIME = 'time', 'tottime'
47*cda5da8dSAndroid Build Coastguard Worker
48*cda5da8dSAndroid Build Coastguard Worker    def __new__(cls, *values):
49*cda5da8dSAndroid Build Coastguard Worker        value = values[0]
50*cda5da8dSAndroid Build Coastguard Worker        obj = str.__new__(cls, value)
51*cda5da8dSAndroid Build Coastguard Worker        obj._value_ = value
52*cda5da8dSAndroid Build Coastguard Worker        for other_value in values[1:]:
53*cda5da8dSAndroid Build Coastguard Worker            cls._value2member_map_[other_value] = obj
54*cda5da8dSAndroid Build Coastguard Worker        obj._all_values = values
55*cda5da8dSAndroid Build Coastguard Worker        return obj
56*cda5da8dSAndroid Build Coastguard Worker
57*cda5da8dSAndroid Build Coastguard Worker
58*cda5da8dSAndroid Build Coastguard Worker@dataclass(unsafe_hash=True)
59*cda5da8dSAndroid Build Coastguard Workerclass FunctionProfile:
60*cda5da8dSAndroid Build Coastguard Worker    ncalls: str
61*cda5da8dSAndroid Build Coastguard Worker    tottime: float
62*cda5da8dSAndroid Build Coastguard Worker    percall_tottime: float
63*cda5da8dSAndroid Build Coastguard Worker    cumtime: float
64*cda5da8dSAndroid Build Coastguard Worker    percall_cumtime: float
65*cda5da8dSAndroid Build Coastguard Worker    file_name: str
66*cda5da8dSAndroid Build Coastguard Worker    line_number: int
67*cda5da8dSAndroid Build Coastguard Worker
68*cda5da8dSAndroid Build Coastguard Worker@dataclass(unsafe_hash=True)
69*cda5da8dSAndroid Build Coastguard Workerclass StatsProfile:
70*cda5da8dSAndroid Build Coastguard Worker    '''Class for keeping track of an item in inventory.'''
71*cda5da8dSAndroid Build Coastguard Worker    total_tt: float
72*cda5da8dSAndroid Build Coastguard Worker    func_profiles: Dict[str, FunctionProfile]
73*cda5da8dSAndroid Build Coastguard Worker
74*cda5da8dSAndroid Build Coastguard Workerclass Stats:
75*cda5da8dSAndroid Build Coastguard Worker    """This class is used for creating reports from data generated by the
76*cda5da8dSAndroid Build Coastguard Worker    Profile class.  It is a "friend" of that class, and imports data either
77*cda5da8dSAndroid Build Coastguard Worker    by direct access to members of Profile class, or by reading in a dictionary
78*cda5da8dSAndroid Build Coastguard Worker    that was emitted (via marshal) from the Profile class.
79*cda5da8dSAndroid Build Coastguard Worker
80*cda5da8dSAndroid Build Coastguard Worker    The big change from the previous Profiler (in terms of raw functionality)
81*cda5da8dSAndroid Build Coastguard Worker    is that an "add()" method has been provided to combine Stats from
82*cda5da8dSAndroid Build Coastguard Worker    several distinct profile runs.  Both the constructor and the add()
83*cda5da8dSAndroid Build Coastguard Worker    method now take arbitrarily many file names as arguments.
84*cda5da8dSAndroid Build Coastguard Worker
85*cda5da8dSAndroid Build Coastguard Worker    All the print methods now take an argument that indicates how many lines
86*cda5da8dSAndroid Build Coastguard Worker    to print.  If the arg is a floating point number between 0 and 1.0, then
87*cda5da8dSAndroid Build Coastguard Worker    it is taken as a decimal percentage of the available lines to be printed
88*cda5da8dSAndroid Build Coastguard Worker    (e.g., .1 means print 10% of all available lines).  If it is an integer,
89*cda5da8dSAndroid Build Coastguard Worker    it is taken to mean the number of lines of data that you wish to have
90*cda5da8dSAndroid Build Coastguard Worker    printed.
91*cda5da8dSAndroid Build Coastguard Worker
92*cda5da8dSAndroid Build Coastguard Worker    The sort_stats() method now processes some additional options (i.e., in
93*cda5da8dSAndroid Build Coastguard Worker    addition to the old -1, 0, 1, or 2 that are respectively interpreted as
94*cda5da8dSAndroid Build Coastguard Worker    'stdname', 'calls', 'time', and 'cumulative').  It takes either an
95*cda5da8dSAndroid Build Coastguard Worker    arbitrary number of quoted strings or SortKey enum to select the sort
96*cda5da8dSAndroid Build Coastguard Worker    order.
97*cda5da8dSAndroid Build Coastguard Worker
98*cda5da8dSAndroid Build Coastguard Worker    For example sort_stats('time', 'name') or sort_stats(SortKey.TIME,
99*cda5da8dSAndroid Build Coastguard Worker    SortKey.NAME) sorts on the major key of 'internal function time', and on
100*cda5da8dSAndroid Build Coastguard Worker    the minor key of 'the name of the function'.  Look at the two tables in
101*cda5da8dSAndroid Build Coastguard Worker    sort_stats() and get_sort_arg_defs(self) for more examples.
102*cda5da8dSAndroid Build Coastguard Worker
103*cda5da8dSAndroid Build Coastguard Worker    All methods return self, so you can string together commands like:
104*cda5da8dSAndroid Build Coastguard Worker        Stats('foo', 'goo').strip_dirs().sort_stats('calls').\
105*cda5da8dSAndroid Build Coastguard Worker                            print_stats(5).print_callers(5)
106*cda5da8dSAndroid Build Coastguard Worker    """
107*cda5da8dSAndroid Build Coastguard Worker
108*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, *args, stream=None):
109*cda5da8dSAndroid Build Coastguard Worker        self.stream = stream or sys.stdout
110*cda5da8dSAndroid Build Coastguard Worker        if not len(args):
111*cda5da8dSAndroid Build Coastguard Worker            arg = None
112*cda5da8dSAndroid Build Coastguard Worker        else:
113*cda5da8dSAndroid Build Coastguard Worker            arg = args[0]
114*cda5da8dSAndroid Build Coastguard Worker            args = args[1:]
115*cda5da8dSAndroid Build Coastguard Worker        self.init(arg)
116*cda5da8dSAndroid Build Coastguard Worker        self.add(*args)
117*cda5da8dSAndroid Build Coastguard Worker
118*cda5da8dSAndroid Build Coastguard Worker    def init(self, arg):
119*cda5da8dSAndroid Build Coastguard Worker        self.all_callees = None  # calc only if needed
120*cda5da8dSAndroid Build Coastguard Worker        self.files = []
121*cda5da8dSAndroid Build Coastguard Worker        self.fcn_list = None
122*cda5da8dSAndroid Build Coastguard Worker        self.total_tt = 0
123*cda5da8dSAndroid Build Coastguard Worker        self.total_calls = 0
124*cda5da8dSAndroid Build Coastguard Worker        self.prim_calls = 0
125*cda5da8dSAndroid Build Coastguard Worker        self.max_name_len = 0
126*cda5da8dSAndroid Build Coastguard Worker        self.top_level = set()
127*cda5da8dSAndroid Build Coastguard Worker        self.stats = {}
128*cda5da8dSAndroid Build Coastguard Worker        self.sort_arg_dict = {}
129*cda5da8dSAndroid Build Coastguard Worker        self.load_stats(arg)
130*cda5da8dSAndroid Build Coastguard Worker        try:
131*cda5da8dSAndroid Build Coastguard Worker            self.get_top_level_stats()
132*cda5da8dSAndroid Build Coastguard Worker        except Exception:
133*cda5da8dSAndroid Build Coastguard Worker            print("Invalid timing data %s" %
134*cda5da8dSAndroid Build Coastguard Worker                  (self.files[-1] if self.files else ''), file=self.stream)
135*cda5da8dSAndroid Build Coastguard Worker            raise
136*cda5da8dSAndroid Build Coastguard Worker
137*cda5da8dSAndroid Build Coastguard Worker    def load_stats(self, arg):
138*cda5da8dSAndroid Build Coastguard Worker        if arg is None:
139*cda5da8dSAndroid Build Coastguard Worker            self.stats = {}
140*cda5da8dSAndroid Build Coastguard Worker            return
141*cda5da8dSAndroid Build Coastguard Worker        elif isinstance(arg, str):
142*cda5da8dSAndroid Build Coastguard Worker            with open(arg, 'rb') as f:
143*cda5da8dSAndroid Build Coastguard Worker                self.stats = marshal.load(f)
144*cda5da8dSAndroid Build Coastguard Worker            try:
145*cda5da8dSAndroid Build Coastguard Worker                file_stats = os.stat(arg)
146*cda5da8dSAndroid Build Coastguard Worker                arg = time.ctime(file_stats.st_mtime) + "    " + arg
147*cda5da8dSAndroid Build Coastguard Worker            except:  # in case this is not unix
148*cda5da8dSAndroid Build Coastguard Worker                pass
149*cda5da8dSAndroid Build Coastguard Worker            self.files = [arg]
150*cda5da8dSAndroid Build Coastguard Worker        elif hasattr(arg, 'create_stats'):
151*cda5da8dSAndroid Build Coastguard Worker            arg.create_stats()
152*cda5da8dSAndroid Build Coastguard Worker            self.stats = arg.stats
153*cda5da8dSAndroid Build Coastguard Worker            arg.stats = {}
154*cda5da8dSAndroid Build Coastguard Worker        if not self.stats:
155*cda5da8dSAndroid Build Coastguard Worker            raise TypeError("Cannot create or construct a %r object from %r"
156*cda5da8dSAndroid Build Coastguard Worker                            % (self.__class__, arg))
157*cda5da8dSAndroid Build Coastguard Worker        return
158*cda5da8dSAndroid Build Coastguard Worker
159*cda5da8dSAndroid Build Coastguard Worker    def get_top_level_stats(self):
160*cda5da8dSAndroid Build Coastguard Worker        for func, (cc, nc, tt, ct, callers) in self.stats.items():
161*cda5da8dSAndroid Build Coastguard Worker            self.total_calls += nc
162*cda5da8dSAndroid Build Coastguard Worker            self.prim_calls  += cc
163*cda5da8dSAndroid Build Coastguard Worker            self.total_tt    += tt
164*cda5da8dSAndroid Build Coastguard Worker            if ("jprofile", 0, "profiler") in callers:
165*cda5da8dSAndroid Build Coastguard Worker                self.top_level.add(func)
166*cda5da8dSAndroid Build Coastguard Worker            if len(func_std_string(func)) > self.max_name_len:
167*cda5da8dSAndroid Build Coastguard Worker                self.max_name_len = len(func_std_string(func))
168*cda5da8dSAndroid Build Coastguard Worker
169*cda5da8dSAndroid Build Coastguard Worker    def add(self, *arg_list):
170*cda5da8dSAndroid Build Coastguard Worker        if not arg_list:
171*cda5da8dSAndroid Build Coastguard Worker            return self
172*cda5da8dSAndroid Build Coastguard Worker        for item in reversed(arg_list):
173*cda5da8dSAndroid Build Coastguard Worker            if type(self) != type(item):
174*cda5da8dSAndroid Build Coastguard Worker                item = Stats(item)
175*cda5da8dSAndroid Build Coastguard Worker            self.files += item.files
176*cda5da8dSAndroid Build Coastguard Worker            self.total_calls += item.total_calls
177*cda5da8dSAndroid Build Coastguard Worker            self.prim_calls += item.prim_calls
178*cda5da8dSAndroid Build Coastguard Worker            self.total_tt += item.total_tt
179*cda5da8dSAndroid Build Coastguard Worker            for func in item.top_level:
180*cda5da8dSAndroid Build Coastguard Worker                self.top_level.add(func)
181*cda5da8dSAndroid Build Coastguard Worker
182*cda5da8dSAndroid Build Coastguard Worker            if self.max_name_len < item.max_name_len:
183*cda5da8dSAndroid Build Coastguard Worker                self.max_name_len = item.max_name_len
184*cda5da8dSAndroid Build Coastguard Worker
185*cda5da8dSAndroid Build Coastguard Worker            self.fcn_list = None
186*cda5da8dSAndroid Build Coastguard Worker
187*cda5da8dSAndroid Build Coastguard Worker            for func, stat in item.stats.items():
188*cda5da8dSAndroid Build Coastguard Worker                if func in self.stats:
189*cda5da8dSAndroid Build Coastguard Worker                    old_func_stat = self.stats[func]
190*cda5da8dSAndroid Build Coastguard Worker                else:
191*cda5da8dSAndroid Build Coastguard Worker                    old_func_stat = (0, 0, 0, 0, {},)
192*cda5da8dSAndroid Build Coastguard Worker                self.stats[func] = add_func_stats(old_func_stat, stat)
193*cda5da8dSAndroid Build Coastguard Worker        return self
194*cda5da8dSAndroid Build Coastguard Worker
195*cda5da8dSAndroid Build Coastguard Worker    def dump_stats(self, filename):
196*cda5da8dSAndroid Build Coastguard Worker        """Write the profile data to a file we know how to load back."""
197*cda5da8dSAndroid Build Coastguard Worker        with open(filename, 'wb') as f:
198*cda5da8dSAndroid Build Coastguard Worker            marshal.dump(self.stats, f)
199*cda5da8dSAndroid Build Coastguard Worker
200*cda5da8dSAndroid Build Coastguard Worker    # list the tuple indices and directions for sorting,
201*cda5da8dSAndroid Build Coastguard Worker    # along with some printable description
202*cda5da8dSAndroid Build Coastguard Worker    sort_arg_dict_default = {
203*cda5da8dSAndroid Build Coastguard Worker              "calls"     : (((1,-1),              ), "call count"),
204*cda5da8dSAndroid Build Coastguard Worker              "ncalls"    : (((1,-1),              ), "call count"),
205*cda5da8dSAndroid Build Coastguard Worker              "cumtime"   : (((3,-1),              ), "cumulative time"),
206*cda5da8dSAndroid Build Coastguard Worker              "cumulative": (((3,-1),              ), "cumulative time"),
207*cda5da8dSAndroid Build Coastguard Worker              "filename"  : (((4, 1),              ), "file name"),
208*cda5da8dSAndroid Build Coastguard Worker              "line"      : (((5, 1),              ), "line number"),
209*cda5da8dSAndroid Build Coastguard Worker              "module"    : (((4, 1),              ), "file name"),
210*cda5da8dSAndroid Build Coastguard Worker              "name"      : (((6, 1),              ), "function name"),
211*cda5da8dSAndroid Build Coastguard Worker              "nfl"       : (((6, 1),(4, 1),(5, 1),), "name/file/line"),
212*cda5da8dSAndroid Build Coastguard Worker              "pcalls"    : (((0,-1),              ), "primitive call count"),
213*cda5da8dSAndroid Build Coastguard Worker              "stdname"   : (((7, 1),              ), "standard name"),
214*cda5da8dSAndroid Build Coastguard Worker              "time"      : (((2,-1),              ), "internal time"),
215*cda5da8dSAndroid Build Coastguard Worker              "tottime"   : (((2,-1),              ), "internal time"),
216*cda5da8dSAndroid Build Coastguard Worker              }
217*cda5da8dSAndroid Build Coastguard Worker
218*cda5da8dSAndroid Build Coastguard Worker    def get_sort_arg_defs(self):
219*cda5da8dSAndroid Build Coastguard Worker        """Expand all abbreviations that are unique."""
220*cda5da8dSAndroid Build Coastguard Worker        if not self.sort_arg_dict:
221*cda5da8dSAndroid Build Coastguard Worker            self.sort_arg_dict = dict = {}
222*cda5da8dSAndroid Build Coastguard Worker            bad_list = {}
223*cda5da8dSAndroid Build Coastguard Worker            for word, tup in self.sort_arg_dict_default.items():
224*cda5da8dSAndroid Build Coastguard Worker                fragment = word
225*cda5da8dSAndroid Build Coastguard Worker                while fragment:
226*cda5da8dSAndroid Build Coastguard Worker                    if not fragment:
227*cda5da8dSAndroid Build Coastguard Worker                        break
228*cda5da8dSAndroid Build Coastguard Worker                    if fragment in dict:
229*cda5da8dSAndroid Build Coastguard Worker                        bad_list[fragment] = 0
230*cda5da8dSAndroid Build Coastguard Worker                        break
231*cda5da8dSAndroid Build Coastguard Worker                    dict[fragment] = tup
232*cda5da8dSAndroid Build Coastguard Worker                    fragment = fragment[:-1]
233*cda5da8dSAndroid Build Coastguard Worker            for word in bad_list:
234*cda5da8dSAndroid Build Coastguard Worker                del dict[word]
235*cda5da8dSAndroid Build Coastguard Worker        return self.sort_arg_dict
236*cda5da8dSAndroid Build Coastguard Worker
237*cda5da8dSAndroid Build Coastguard Worker    def sort_stats(self, *field):
238*cda5da8dSAndroid Build Coastguard Worker        if not field:
239*cda5da8dSAndroid Build Coastguard Worker            self.fcn_list = 0
240*cda5da8dSAndroid Build Coastguard Worker            return self
241*cda5da8dSAndroid Build Coastguard Worker        if len(field) == 1 and isinstance(field[0], int):
242*cda5da8dSAndroid Build Coastguard Worker            # Be compatible with old profiler
243*cda5da8dSAndroid Build Coastguard Worker            field = [ {-1: "stdname",
244*cda5da8dSAndroid Build Coastguard Worker                       0:  "calls",
245*cda5da8dSAndroid Build Coastguard Worker                       1:  "time",
246*cda5da8dSAndroid Build Coastguard Worker                       2:  "cumulative"}[field[0]] ]
247*cda5da8dSAndroid Build Coastguard Worker        elif len(field) >= 2:
248*cda5da8dSAndroid Build Coastguard Worker            for arg in field[1:]:
249*cda5da8dSAndroid Build Coastguard Worker                if type(arg) != type(field[0]):
250*cda5da8dSAndroid Build Coastguard Worker                    raise TypeError("Can't have mixed argument type")
251*cda5da8dSAndroid Build Coastguard Worker
252*cda5da8dSAndroid Build Coastguard Worker        sort_arg_defs = self.get_sort_arg_defs()
253*cda5da8dSAndroid Build Coastguard Worker
254*cda5da8dSAndroid Build Coastguard Worker        sort_tuple = ()
255*cda5da8dSAndroid Build Coastguard Worker        self.sort_type = ""
256*cda5da8dSAndroid Build Coastguard Worker        connector = ""
257*cda5da8dSAndroid Build Coastguard Worker        for word in field:
258*cda5da8dSAndroid Build Coastguard Worker            if isinstance(word, SortKey):
259*cda5da8dSAndroid Build Coastguard Worker                word = word.value
260*cda5da8dSAndroid Build Coastguard Worker            sort_tuple = sort_tuple + sort_arg_defs[word][0]
261*cda5da8dSAndroid Build Coastguard Worker            self.sort_type += connector + sort_arg_defs[word][1]
262*cda5da8dSAndroid Build Coastguard Worker            connector = ", "
263*cda5da8dSAndroid Build Coastguard Worker
264*cda5da8dSAndroid Build Coastguard Worker        stats_list = []
265*cda5da8dSAndroid Build Coastguard Worker        for func, (cc, nc, tt, ct, callers) in self.stats.items():
266*cda5da8dSAndroid Build Coastguard Worker            stats_list.append((cc, nc, tt, ct) + func +
267*cda5da8dSAndroid Build Coastguard Worker                              (func_std_string(func), func))
268*cda5da8dSAndroid Build Coastguard Worker
269*cda5da8dSAndroid Build Coastguard Worker        stats_list.sort(key=cmp_to_key(TupleComp(sort_tuple).compare))
270*cda5da8dSAndroid Build Coastguard Worker
271*cda5da8dSAndroid Build Coastguard Worker        self.fcn_list = fcn_list = []
272*cda5da8dSAndroid Build Coastguard Worker        for tuple in stats_list:
273*cda5da8dSAndroid Build Coastguard Worker            fcn_list.append(tuple[-1])
274*cda5da8dSAndroid Build Coastguard Worker        return self
275*cda5da8dSAndroid Build Coastguard Worker
276*cda5da8dSAndroid Build Coastguard Worker    def reverse_order(self):
277*cda5da8dSAndroid Build Coastguard Worker        if self.fcn_list:
278*cda5da8dSAndroid Build Coastguard Worker            self.fcn_list.reverse()
279*cda5da8dSAndroid Build Coastguard Worker        return self
280*cda5da8dSAndroid Build Coastguard Worker
281*cda5da8dSAndroid Build Coastguard Worker    def strip_dirs(self):
282*cda5da8dSAndroid Build Coastguard Worker        oldstats = self.stats
283*cda5da8dSAndroid Build Coastguard Worker        self.stats = newstats = {}
284*cda5da8dSAndroid Build Coastguard Worker        max_name_len = 0
285*cda5da8dSAndroid Build Coastguard Worker        for func, (cc, nc, tt, ct, callers) in oldstats.items():
286*cda5da8dSAndroid Build Coastguard Worker            newfunc = func_strip_path(func)
287*cda5da8dSAndroid Build Coastguard Worker            if len(func_std_string(newfunc)) > max_name_len:
288*cda5da8dSAndroid Build Coastguard Worker                max_name_len = len(func_std_string(newfunc))
289*cda5da8dSAndroid Build Coastguard Worker            newcallers = {}
290*cda5da8dSAndroid Build Coastguard Worker            for func2, caller in callers.items():
291*cda5da8dSAndroid Build Coastguard Worker                newcallers[func_strip_path(func2)] = caller
292*cda5da8dSAndroid Build Coastguard Worker
293*cda5da8dSAndroid Build Coastguard Worker            if newfunc in newstats:
294*cda5da8dSAndroid Build Coastguard Worker                newstats[newfunc] = add_func_stats(
295*cda5da8dSAndroid Build Coastguard Worker                                        newstats[newfunc],
296*cda5da8dSAndroid Build Coastguard Worker                                        (cc, nc, tt, ct, newcallers))
297*cda5da8dSAndroid Build Coastguard Worker            else:
298*cda5da8dSAndroid Build Coastguard Worker                newstats[newfunc] = (cc, nc, tt, ct, newcallers)
299*cda5da8dSAndroid Build Coastguard Worker        old_top = self.top_level
300*cda5da8dSAndroid Build Coastguard Worker        self.top_level = new_top = set()
301*cda5da8dSAndroid Build Coastguard Worker        for func in old_top:
302*cda5da8dSAndroid Build Coastguard Worker            new_top.add(func_strip_path(func))
303*cda5da8dSAndroid Build Coastguard Worker
304*cda5da8dSAndroid Build Coastguard Worker        self.max_name_len = max_name_len
305*cda5da8dSAndroid Build Coastguard Worker
306*cda5da8dSAndroid Build Coastguard Worker        self.fcn_list = None
307*cda5da8dSAndroid Build Coastguard Worker        self.all_callees = None
308*cda5da8dSAndroid Build Coastguard Worker        return self
309*cda5da8dSAndroid Build Coastguard Worker
310*cda5da8dSAndroid Build Coastguard Worker    def calc_callees(self):
311*cda5da8dSAndroid Build Coastguard Worker        if self.all_callees:
312*cda5da8dSAndroid Build Coastguard Worker            return
313*cda5da8dSAndroid Build Coastguard Worker        self.all_callees = all_callees = {}
314*cda5da8dSAndroid Build Coastguard Worker        for func, (cc, nc, tt, ct, callers) in self.stats.items():
315*cda5da8dSAndroid Build Coastguard Worker            if not func in all_callees:
316*cda5da8dSAndroid Build Coastguard Worker                all_callees[func] = {}
317*cda5da8dSAndroid Build Coastguard Worker            for func2, caller in callers.items():
318*cda5da8dSAndroid Build Coastguard Worker                if not func2 in all_callees:
319*cda5da8dSAndroid Build Coastguard Worker                    all_callees[func2] = {}
320*cda5da8dSAndroid Build Coastguard Worker                all_callees[func2][func]  = caller
321*cda5da8dSAndroid Build Coastguard Worker        return
322*cda5da8dSAndroid Build Coastguard Worker
323*cda5da8dSAndroid Build Coastguard Worker    #******************************************************************
324*cda5da8dSAndroid Build Coastguard Worker    # The following functions support actual printing of reports
325*cda5da8dSAndroid Build Coastguard Worker    #******************************************************************
326*cda5da8dSAndroid Build Coastguard Worker
327*cda5da8dSAndroid Build Coastguard Worker    # Optional "amount" is either a line count, or a percentage of lines.
328*cda5da8dSAndroid Build Coastguard Worker
329*cda5da8dSAndroid Build Coastguard Worker    def eval_print_amount(self, sel, list, msg):
330*cda5da8dSAndroid Build Coastguard Worker        new_list = list
331*cda5da8dSAndroid Build Coastguard Worker        if isinstance(sel, str):
332*cda5da8dSAndroid Build Coastguard Worker            try:
333*cda5da8dSAndroid Build Coastguard Worker                rex = re.compile(sel)
334*cda5da8dSAndroid Build Coastguard Worker            except re.error:
335*cda5da8dSAndroid Build Coastguard Worker                msg += "   <Invalid regular expression %r>\n" % sel
336*cda5da8dSAndroid Build Coastguard Worker                return new_list, msg
337*cda5da8dSAndroid Build Coastguard Worker            new_list = []
338*cda5da8dSAndroid Build Coastguard Worker            for func in list:
339*cda5da8dSAndroid Build Coastguard Worker                if rex.search(func_std_string(func)):
340*cda5da8dSAndroid Build Coastguard Worker                    new_list.append(func)
341*cda5da8dSAndroid Build Coastguard Worker        else:
342*cda5da8dSAndroid Build Coastguard Worker            count = len(list)
343*cda5da8dSAndroid Build Coastguard Worker            if isinstance(sel, float) and 0.0 <= sel < 1.0:
344*cda5da8dSAndroid Build Coastguard Worker                count = int(count * sel + .5)
345*cda5da8dSAndroid Build Coastguard Worker                new_list = list[:count]
346*cda5da8dSAndroid Build Coastguard Worker            elif isinstance(sel, int) and 0 <= sel < count:
347*cda5da8dSAndroid Build Coastguard Worker                count = sel
348*cda5da8dSAndroid Build Coastguard Worker                new_list = list[:count]
349*cda5da8dSAndroid Build Coastguard Worker        if len(list) != len(new_list):
350*cda5da8dSAndroid Build Coastguard Worker            msg += "   List reduced from %r to %r due to restriction <%r>\n" % (
351*cda5da8dSAndroid Build Coastguard Worker                len(list), len(new_list), sel)
352*cda5da8dSAndroid Build Coastguard Worker
353*cda5da8dSAndroid Build Coastguard Worker        return new_list, msg
354*cda5da8dSAndroid Build Coastguard Worker
355*cda5da8dSAndroid Build Coastguard Worker    def get_stats_profile(self):
356*cda5da8dSAndroid Build Coastguard Worker        """This method returns an instance of StatsProfile, which contains a mapping
357*cda5da8dSAndroid Build Coastguard Worker        of function names to instances of FunctionProfile. Each FunctionProfile
358*cda5da8dSAndroid Build Coastguard Worker        instance holds information related to the function's profile such as how
359*cda5da8dSAndroid Build Coastguard Worker        long the function took to run, how many times it was called, etc...
360*cda5da8dSAndroid Build Coastguard Worker        """
361*cda5da8dSAndroid Build Coastguard Worker        func_list = self.fcn_list[:] if self.fcn_list else list(self.stats.keys())
362*cda5da8dSAndroid Build Coastguard Worker        if not func_list:
363*cda5da8dSAndroid Build Coastguard Worker            return StatsProfile(0, {})
364*cda5da8dSAndroid Build Coastguard Worker
365*cda5da8dSAndroid Build Coastguard Worker        total_tt = float(f8(self.total_tt))
366*cda5da8dSAndroid Build Coastguard Worker        func_profiles = {}
367*cda5da8dSAndroid Build Coastguard Worker        stats_profile = StatsProfile(total_tt, func_profiles)
368*cda5da8dSAndroid Build Coastguard Worker
369*cda5da8dSAndroid Build Coastguard Worker        for func in func_list:
370*cda5da8dSAndroid Build Coastguard Worker            cc, nc, tt, ct, callers = self.stats[func]
371*cda5da8dSAndroid Build Coastguard Worker            file_name, line_number, func_name = func
372*cda5da8dSAndroid Build Coastguard Worker            ncalls = str(nc) if nc == cc else (str(nc) + '/' + str(cc))
373*cda5da8dSAndroid Build Coastguard Worker            tottime = float(f8(tt))
374*cda5da8dSAndroid Build Coastguard Worker            percall_tottime = -1 if nc == 0 else float(f8(tt/nc))
375*cda5da8dSAndroid Build Coastguard Worker            cumtime = float(f8(ct))
376*cda5da8dSAndroid Build Coastguard Worker            percall_cumtime = -1 if cc == 0 else float(f8(ct/cc))
377*cda5da8dSAndroid Build Coastguard Worker            func_profile = FunctionProfile(
378*cda5da8dSAndroid Build Coastguard Worker                ncalls,
379*cda5da8dSAndroid Build Coastguard Worker                tottime, # time spent in this function alone
380*cda5da8dSAndroid Build Coastguard Worker                percall_tottime,
381*cda5da8dSAndroid Build Coastguard Worker                cumtime, # time spent in the function plus all functions that this function called,
382*cda5da8dSAndroid Build Coastguard Worker                percall_cumtime,
383*cda5da8dSAndroid Build Coastguard Worker                file_name,
384*cda5da8dSAndroid Build Coastguard Worker                line_number
385*cda5da8dSAndroid Build Coastguard Worker            )
386*cda5da8dSAndroid Build Coastguard Worker            func_profiles[func_name] = func_profile
387*cda5da8dSAndroid Build Coastguard Worker
388*cda5da8dSAndroid Build Coastguard Worker        return stats_profile
389*cda5da8dSAndroid Build Coastguard Worker
390*cda5da8dSAndroid Build Coastguard Worker    def get_print_list(self, sel_list):
391*cda5da8dSAndroid Build Coastguard Worker        width = self.max_name_len
392*cda5da8dSAndroid Build Coastguard Worker        if self.fcn_list:
393*cda5da8dSAndroid Build Coastguard Worker            stat_list = self.fcn_list[:]
394*cda5da8dSAndroid Build Coastguard Worker            msg = "   Ordered by: " + self.sort_type + '\n'
395*cda5da8dSAndroid Build Coastguard Worker        else:
396*cda5da8dSAndroid Build Coastguard Worker            stat_list = list(self.stats.keys())
397*cda5da8dSAndroid Build Coastguard Worker            msg = "   Random listing order was used\n"
398*cda5da8dSAndroid Build Coastguard Worker
399*cda5da8dSAndroid Build Coastguard Worker        for selection in sel_list:
400*cda5da8dSAndroid Build Coastguard Worker            stat_list, msg = self.eval_print_amount(selection, stat_list, msg)
401*cda5da8dSAndroid Build Coastguard Worker
402*cda5da8dSAndroid Build Coastguard Worker        count = len(stat_list)
403*cda5da8dSAndroid Build Coastguard Worker
404*cda5da8dSAndroid Build Coastguard Worker        if not stat_list:
405*cda5da8dSAndroid Build Coastguard Worker            return 0, stat_list
406*cda5da8dSAndroid Build Coastguard Worker        print(msg, file=self.stream)
407*cda5da8dSAndroid Build Coastguard Worker        if count < len(self.stats):
408*cda5da8dSAndroid Build Coastguard Worker            width = 0
409*cda5da8dSAndroid Build Coastguard Worker            for func in stat_list:
410*cda5da8dSAndroid Build Coastguard Worker                if  len(func_std_string(func)) > width:
411*cda5da8dSAndroid Build Coastguard Worker                    width = len(func_std_string(func))
412*cda5da8dSAndroid Build Coastguard Worker        return width+2, stat_list
413*cda5da8dSAndroid Build Coastguard Worker
414*cda5da8dSAndroid Build Coastguard Worker    def print_stats(self, *amount):
415*cda5da8dSAndroid Build Coastguard Worker        for filename in self.files:
416*cda5da8dSAndroid Build Coastguard Worker            print(filename, file=self.stream)
417*cda5da8dSAndroid Build Coastguard Worker        if self.files:
418*cda5da8dSAndroid Build Coastguard Worker            print(file=self.stream)
419*cda5da8dSAndroid Build Coastguard Worker        indent = ' ' * 8
420*cda5da8dSAndroid Build Coastguard Worker        for func in self.top_level:
421*cda5da8dSAndroid Build Coastguard Worker            print(indent, func_get_function_name(func), file=self.stream)
422*cda5da8dSAndroid Build Coastguard Worker
423*cda5da8dSAndroid Build Coastguard Worker        print(indent, self.total_calls, "function calls", end=' ', file=self.stream)
424*cda5da8dSAndroid Build Coastguard Worker        if self.total_calls != self.prim_calls:
425*cda5da8dSAndroid Build Coastguard Worker            print("(%d primitive calls)" % self.prim_calls, end=' ', file=self.stream)
426*cda5da8dSAndroid Build Coastguard Worker        print("in %.3f seconds" % self.total_tt, file=self.stream)
427*cda5da8dSAndroid Build Coastguard Worker        print(file=self.stream)
428*cda5da8dSAndroid Build Coastguard Worker        width, list = self.get_print_list(amount)
429*cda5da8dSAndroid Build Coastguard Worker        if list:
430*cda5da8dSAndroid Build Coastguard Worker            self.print_title()
431*cda5da8dSAndroid Build Coastguard Worker            for func in list:
432*cda5da8dSAndroid Build Coastguard Worker                self.print_line(func)
433*cda5da8dSAndroid Build Coastguard Worker            print(file=self.stream)
434*cda5da8dSAndroid Build Coastguard Worker            print(file=self.stream)
435*cda5da8dSAndroid Build Coastguard Worker        return self
436*cda5da8dSAndroid Build Coastguard Worker
437*cda5da8dSAndroid Build Coastguard Worker    def print_callees(self, *amount):
438*cda5da8dSAndroid Build Coastguard Worker        width, list = self.get_print_list(amount)
439*cda5da8dSAndroid Build Coastguard Worker        if list:
440*cda5da8dSAndroid Build Coastguard Worker            self.calc_callees()
441*cda5da8dSAndroid Build Coastguard Worker
442*cda5da8dSAndroid Build Coastguard Worker            self.print_call_heading(width, "called...")
443*cda5da8dSAndroid Build Coastguard Worker            for func in list:
444*cda5da8dSAndroid Build Coastguard Worker                if func in self.all_callees:
445*cda5da8dSAndroid Build Coastguard Worker                    self.print_call_line(width, func, self.all_callees[func])
446*cda5da8dSAndroid Build Coastguard Worker                else:
447*cda5da8dSAndroid Build Coastguard Worker                    self.print_call_line(width, func, {})
448*cda5da8dSAndroid Build Coastguard Worker            print(file=self.stream)
449*cda5da8dSAndroid Build Coastguard Worker            print(file=self.stream)
450*cda5da8dSAndroid Build Coastguard Worker        return self
451*cda5da8dSAndroid Build Coastguard Worker
452*cda5da8dSAndroid Build Coastguard Worker    def print_callers(self, *amount):
453*cda5da8dSAndroid Build Coastguard Worker        width, list = self.get_print_list(amount)
454*cda5da8dSAndroid Build Coastguard Worker        if list:
455*cda5da8dSAndroid Build Coastguard Worker            self.print_call_heading(width, "was called by...")
456*cda5da8dSAndroid Build Coastguard Worker            for func in list:
457*cda5da8dSAndroid Build Coastguard Worker                cc, nc, tt, ct, callers = self.stats[func]
458*cda5da8dSAndroid Build Coastguard Worker                self.print_call_line(width, func, callers, "<-")
459*cda5da8dSAndroid Build Coastguard Worker            print(file=self.stream)
460*cda5da8dSAndroid Build Coastguard Worker            print(file=self.stream)
461*cda5da8dSAndroid Build Coastguard Worker        return self
462*cda5da8dSAndroid Build Coastguard Worker
463*cda5da8dSAndroid Build Coastguard Worker    def print_call_heading(self, name_size, column_title):
464*cda5da8dSAndroid Build Coastguard Worker        print("Function ".ljust(name_size) + column_title, file=self.stream)
465*cda5da8dSAndroid Build Coastguard Worker        # print sub-header only if we have new-style callers
466*cda5da8dSAndroid Build Coastguard Worker        subheader = False
467*cda5da8dSAndroid Build Coastguard Worker        for cc, nc, tt, ct, callers in self.stats.values():
468*cda5da8dSAndroid Build Coastguard Worker            if callers:
469*cda5da8dSAndroid Build Coastguard Worker                value = next(iter(callers.values()))
470*cda5da8dSAndroid Build Coastguard Worker                subheader = isinstance(value, tuple)
471*cda5da8dSAndroid Build Coastguard Worker                break
472*cda5da8dSAndroid Build Coastguard Worker        if subheader:
473*cda5da8dSAndroid Build Coastguard Worker            print(" "*name_size + "    ncalls  tottime  cumtime", file=self.stream)
474*cda5da8dSAndroid Build Coastguard Worker
475*cda5da8dSAndroid Build Coastguard Worker    def print_call_line(self, name_size, source, call_dict, arrow="->"):
476*cda5da8dSAndroid Build Coastguard Worker        print(func_std_string(source).ljust(name_size) + arrow, end=' ', file=self.stream)
477*cda5da8dSAndroid Build Coastguard Worker        if not call_dict:
478*cda5da8dSAndroid Build Coastguard Worker            print(file=self.stream)
479*cda5da8dSAndroid Build Coastguard Worker            return
480*cda5da8dSAndroid Build Coastguard Worker        clist = sorted(call_dict.keys())
481*cda5da8dSAndroid Build Coastguard Worker        indent = ""
482*cda5da8dSAndroid Build Coastguard Worker        for func in clist:
483*cda5da8dSAndroid Build Coastguard Worker            name = func_std_string(func)
484*cda5da8dSAndroid Build Coastguard Worker            value = call_dict[func]
485*cda5da8dSAndroid Build Coastguard Worker            if isinstance(value, tuple):
486*cda5da8dSAndroid Build Coastguard Worker                nc, cc, tt, ct = value
487*cda5da8dSAndroid Build Coastguard Worker                if nc != cc:
488*cda5da8dSAndroid Build Coastguard Worker                    substats = '%d/%d' % (nc, cc)
489*cda5da8dSAndroid Build Coastguard Worker                else:
490*cda5da8dSAndroid Build Coastguard Worker                    substats = '%d' % (nc,)
491*cda5da8dSAndroid Build Coastguard Worker                substats = '%s %s %s  %s' % (substats.rjust(7+2*len(indent)),
492*cda5da8dSAndroid Build Coastguard Worker                                             f8(tt), f8(ct), name)
493*cda5da8dSAndroid Build Coastguard Worker                left_width = name_size + 1
494*cda5da8dSAndroid Build Coastguard Worker            else:
495*cda5da8dSAndroid Build Coastguard Worker                substats = '%s(%r) %s' % (name, value, f8(self.stats[func][3]))
496*cda5da8dSAndroid Build Coastguard Worker                left_width = name_size + 3
497*cda5da8dSAndroid Build Coastguard Worker            print(indent*left_width + substats, file=self.stream)
498*cda5da8dSAndroid Build Coastguard Worker            indent = " "
499*cda5da8dSAndroid Build Coastguard Worker
500*cda5da8dSAndroid Build Coastguard Worker    def print_title(self):
501*cda5da8dSAndroid Build Coastguard Worker        print('   ncalls  tottime  percall  cumtime  percall', end=' ', file=self.stream)
502*cda5da8dSAndroid Build Coastguard Worker        print('filename:lineno(function)', file=self.stream)
503*cda5da8dSAndroid Build Coastguard Worker
504*cda5da8dSAndroid Build Coastguard Worker    def print_line(self, func):  # hack: should print percentages
505*cda5da8dSAndroid Build Coastguard Worker        cc, nc, tt, ct, callers = self.stats[func]
506*cda5da8dSAndroid Build Coastguard Worker        c = str(nc)
507*cda5da8dSAndroid Build Coastguard Worker        if nc != cc:
508*cda5da8dSAndroid Build Coastguard Worker            c = c + '/' + str(cc)
509*cda5da8dSAndroid Build Coastguard Worker        print(c.rjust(9), end=' ', file=self.stream)
510*cda5da8dSAndroid Build Coastguard Worker        print(f8(tt), end=' ', file=self.stream)
511*cda5da8dSAndroid Build Coastguard Worker        if nc == 0:
512*cda5da8dSAndroid Build Coastguard Worker            print(' '*8, end=' ', file=self.stream)
513*cda5da8dSAndroid Build Coastguard Worker        else:
514*cda5da8dSAndroid Build Coastguard Worker            print(f8(tt/nc), end=' ', file=self.stream)
515*cda5da8dSAndroid Build Coastguard Worker        print(f8(ct), end=' ', file=self.stream)
516*cda5da8dSAndroid Build Coastguard Worker        if cc == 0:
517*cda5da8dSAndroid Build Coastguard Worker            print(' '*8, end=' ', file=self.stream)
518*cda5da8dSAndroid Build Coastguard Worker        else:
519*cda5da8dSAndroid Build Coastguard Worker            print(f8(ct/cc), end=' ', file=self.stream)
520*cda5da8dSAndroid Build Coastguard Worker        print(func_std_string(func), file=self.stream)
521*cda5da8dSAndroid Build Coastguard Worker
522*cda5da8dSAndroid Build Coastguard Workerclass TupleComp:
523*cda5da8dSAndroid Build Coastguard Worker    """This class provides a generic function for comparing any two tuples.
524*cda5da8dSAndroid Build Coastguard Worker    Each instance records a list of tuple-indices (from most significant
525*cda5da8dSAndroid Build Coastguard Worker    to least significant), and sort direction (ascending or descending) for
526*cda5da8dSAndroid Build Coastguard Worker    each tuple-index.  The compare functions can then be used as the function
527*cda5da8dSAndroid Build Coastguard Worker    argument to the system sort() function when a list of tuples need to be
528*cda5da8dSAndroid Build Coastguard Worker    sorted in the instances order."""
529*cda5da8dSAndroid Build Coastguard Worker
530*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, comp_select_list):
531*cda5da8dSAndroid Build Coastguard Worker        self.comp_select_list = comp_select_list
532*cda5da8dSAndroid Build Coastguard Worker
533*cda5da8dSAndroid Build Coastguard Worker    def compare (self, left, right):
534*cda5da8dSAndroid Build Coastguard Worker        for index, direction in self.comp_select_list:
535*cda5da8dSAndroid Build Coastguard Worker            l = left[index]
536*cda5da8dSAndroid Build Coastguard Worker            r = right[index]
537*cda5da8dSAndroid Build Coastguard Worker            if l < r:
538*cda5da8dSAndroid Build Coastguard Worker                return -direction
539*cda5da8dSAndroid Build Coastguard Worker            if l > r:
540*cda5da8dSAndroid Build Coastguard Worker                return direction
541*cda5da8dSAndroid Build Coastguard Worker        return 0
542*cda5da8dSAndroid Build Coastguard Worker
543*cda5da8dSAndroid Build Coastguard Worker
544*cda5da8dSAndroid Build Coastguard Worker#**************************************************************************
545*cda5da8dSAndroid Build Coastguard Worker# func_name is a triple (file:string, line:int, name:string)
546*cda5da8dSAndroid Build Coastguard Worker
547*cda5da8dSAndroid Build Coastguard Workerdef func_strip_path(func_name):
548*cda5da8dSAndroid Build Coastguard Worker    filename, line, name = func_name
549*cda5da8dSAndroid Build Coastguard Worker    return os.path.basename(filename), line, name
550*cda5da8dSAndroid Build Coastguard Worker
551*cda5da8dSAndroid Build Coastguard Workerdef func_get_function_name(func):
552*cda5da8dSAndroid Build Coastguard Worker    return func[2]
553*cda5da8dSAndroid Build Coastguard Worker
554*cda5da8dSAndroid Build Coastguard Workerdef func_std_string(func_name): # match what old profile produced
555*cda5da8dSAndroid Build Coastguard Worker    if func_name[:2] == ('~', 0):
556*cda5da8dSAndroid Build Coastguard Worker        # special case for built-in functions
557*cda5da8dSAndroid Build Coastguard Worker        name = func_name[2]
558*cda5da8dSAndroid Build Coastguard Worker        if name.startswith('<') and name.endswith('>'):
559*cda5da8dSAndroid Build Coastguard Worker            return '{%s}' % name[1:-1]
560*cda5da8dSAndroid Build Coastguard Worker        else:
561*cda5da8dSAndroid Build Coastguard Worker            return name
562*cda5da8dSAndroid Build Coastguard Worker    else:
563*cda5da8dSAndroid Build Coastguard Worker        return "%s:%d(%s)" % func_name
564*cda5da8dSAndroid Build Coastguard Worker
565*cda5da8dSAndroid Build Coastguard Worker#**************************************************************************
566*cda5da8dSAndroid Build Coastguard Worker# The following functions combine statistics for pairs functions.
567*cda5da8dSAndroid Build Coastguard Worker# The bulk of the processing involves correctly handling "call" lists,
568*cda5da8dSAndroid Build Coastguard Worker# such as callers and callees.
569*cda5da8dSAndroid Build Coastguard Worker#**************************************************************************
570*cda5da8dSAndroid Build Coastguard Worker
571*cda5da8dSAndroid Build Coastguard Workerdef add_func_stats(target, source):
572*cda5da8dSAndroid Build Coastguard Worker    """Add together all the stats for two profile entries."""
573*cda5da8dSAndroid Build Coastguard Worker    cc, nc, tt, ct, callers = source
574*cda5da8dSAndroid Build Coastguard Worker    t_cc, t_nc, t_tt, t_ct, t_callers = target
575*cda5da8dSAndroid Build Coastguard Worker    return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct,
576*cda5da8dSAndroid Build Coastguard Worker              add_callers(t_callers, callers))
577*cda5da8dSAndroid Build Coastguard Worker
578*cda5da8dSAndroid Build Coastguard Workerdef add_callers(target, source):
579*cda5da8dSAndroid Build Coastguard Worker    """Combine two caller lists in a single list."""
580*cda5da8dSAndroid Build Coastguard Worker    new_callers = {}
581*cda5da8dSAndroid Build Coastguard Worker    for func, caller in target.items():
582*cda5da8dSAndroid Build Coastguard Worker        new_callers[func] = caller
583*cda5da8dSAndroid Build Coastguard Worker    for func, caller in source.items():
584*cda5da8dSAndroid Build Coastguard Worker        if func in new_callers:
585*cda5da8dSAndroid Build Coastguard Worker            if isinstance(caller, tuple):
586*cda5da8dSAndroid Build Coastguard Worker                # format used by cProfile
587*cda5da8dSAndroid Build Coastguard Worker                new_callers[func] = tuple(i + j for i, j in zip(caller, new_callers[func]))
588*cda5da8dSAndroid Build Coastguard Worker            else:
589*cda5da8dSAndroid Build Coastguard Worker                # format used by profile
590*cda5da8dSAndroid Build Coastguard Worker                new_callers[func] += caller
591*cda5da8dSAndroid Build Coastguard Worker        else:
592*cda5da8dSAndroid Build Coastguard Worker            new_callers[func] = caller
593*cda5da8dSAndroid Build Coastguard Worker    return new_callers
594*cda5da8dSAndroid Build Coastguard Worker
595*cda5da8dSAndroid Build Coastguard Workerdef count_calls(callers):
596*cda5da8dSAndroid Build Coastguard Worker    """Sum the caller statistics to get total number of calls received."""
597*cda5da8dSAndroid Build Coastguard Worker    nc = 0
598*cda5da8dSAndroid Build Coastguard Worker    for calls in callers.values():
599*cda5da8dSAndroid Build Coastguard Worker        nc += calls
600*cda5da8dSAndroid Build Coastguard Worker    return nc
601*cda5da8dSAndroid Build Coastguard Worker
602*cda5da8dSAndroid Build Coastguard Worker#**************************************************************************
603*cda5da8dSAndroid Build Coastguard Worker# The following functions support printing of reports
604*cda5da8dSAndroid Build Coastguard Worker#**************************************************************************
605*cda5da8dSAndroid Build Coastguard Worker
606*cda5da8dSAndroid Build Coastguard Workerdef f8(x):
607*cda5da8dSAndroid Build Coastguard Worker    return "%8.3f" % x
608*cda5da8dSAndroid Build Coastguard Worker
609*cda5da8dSAndroid Build Coastguard Worker#**************************************************************************
610*cda5da8dSAndroid Build Coastguard Worker# Statistics browser added by ESR, April 2001
611*cda5da8dSAndroid Build Coastguard Worker#**************************************************************************
612*cda5da8dSAndroid Build Coastguard Worker
613*cda5da8dSAndroid Build Coastguard Workerif __name__ == '__main__':
614*cda5da8dSAndroid Build Coastguard Worker    import cmd
615*cda5da8dSAndroid Build Coastguard Worker    try:
616*cda5da8dSAndroid Build Coastguard Worker        import readline
617*cda5da8dSAndroid Build Coastguard Worker    except ImportError:
618*cda5da8dSAndroid Build Coastguard Worker        pass
619*cda5da8dSAndroid Build Coastguard Worker
620*cda5da8dSAndroid Build Coastguard Worker    class ProfileBrowser(cmd.Cmd):
621*cda5da8dSAndroid Build Coastguard Worker        def __init__(self, profile=None):
622*cda5da8dSAndroid Build Coastguard Worker            cmd.Cmd.__init__(self)
623*cda5da8dSAndroid Build Coastguard Worker            self.prompt = "% "
624*cda5da8dSAndroid Build Coastguard Worker            self.stats = None
625*cda5da8dSAndroid Build Coastguard Worker            self.stream = sys.stdout
626*cda5da8dSAndroid Build Coastguard Worker            if profile is not None:
627*cda5da8dSAndroid Build Coastguard Worker                self.do_read(profile)
628*cda5da8dSAndroid Build Coastguard Worker
629*cda5da8dSAndroid Build Coastguard Worker        def generic(self, fn, line):
630*cda5da8dSAndroid Build Coastguard Worker            args = line.split()
631*cda5da8dSAndroid Build Coastguard Worker            processed = []
632*cda5da8dSAndroid Build Coastguard Worker            for term in args:
633*cda5da8dSAndroid Build Coastguard Worker                try:
634*cda5da8dSAndroid Build Coastguard Worker                    processed.append(int(term))
635*cda5da8dSAndroid Build Coastguard Worker                    continue
636*cda5da8dSAndroid Build Coastguard Worker                except ValueError:
637*cda5da8dSAndroid Build Coastguard Worker                    pass
638*cda5da8dSAndroid Build Coastguard Worker                try:
639*cda5da8dSAndroid Build Coastguard Worker                    frac = float(term)
640*cda5da8dSAndroid Build Coastguard Worker                    if frac > 1 or frac < 0:
641*cda5da8dSAndroid Build Coastguard Worker                        print("Fraction argument must be in [0, 1]", file=self.stream)
642*cda5da8dSAndroid Build Coastguard Worker                        continue
643*cda5da8dSAndroid Build Coastguard Worker                    processed.append(frac)
644*cda5da8dSAndroid Build Coastguard Worker                    continue
645*cda5da8dSAndroid Build Coastguard Worker                except ValueError:
646*cda5da8dSAndroid Build Coastguard Worker                    pass
647*cda5da8dSAndroid Build Coastguard Worker                processed.append(term)
648*cda5da8dSAndroid Build Coastguard Worker            if self.stats:
649*cda5da8dSAndroid Build Coastguard Worker                getattr(self.stats, fn)(*processed)
650*cda5da8dSAndroid Build Coastguard Worker            else:
651*cda5da8dSAndroid Build Coastguard Worker                print("No statistics object is loaded.", file=self.stream)
652*cda5da8dSAndroid Build Coastguard Worker            return 0
653*cda5da8dSAndroid Build Coastguard Worker        def generic_help(self):
654*cda5da8dSAndroid Build Coastguard Worker            print("Arguments may be:", file=self.stream)
655*cda5da8dSAndroid Build Coastguard Worker            print("* An integer maximum number of entries to print.", file=self.stream)
656*cda5da8dSAndroid Build Coastguard Worker            print("* A decimal fractional number between 0 and 1, controlling", file=self.stream)
657*cda5da8dSAndroid Build Coastguard Worker            print("  what fraction of selected entries to print.", file=self.stream)
658*cda5da8dSAndroid Build Coastguard Worker            print("* A regular expression; only entries with function names", file=self.stream)
659*cda5da8dSAndroid Build Coastguard Worker            print("  that match it are printed.", file=self.stream)
660*cda5da8dSAndroid Build Coastguard Worker
661*cda5da8dSAndroid Build Coastguard Worker        def do_add(self, line):
662*cda5da8dSAndroid Build Coastguard Worker            if self.stats:
663*cda5da8dSAndroid Build Coastguard Worker                try:
664*cda5da8dSAndroid Build Coastguard Worker                    self.stats.add(line)
665*cda5da8dSAndroid Build Coastguard Worker                except OSError as e:
666*cda5da8dSAndroid Build Coastguard Worker                    print("Failed to load statistics for %s: %s" % (line, e), file=self.stream)
667*cda5da8dSAndroid Build Coastguard Worker            else:
668*cda5da8dSAndroid Build Coastguard Worker                print("No statistics object is loaded.", file=self.stream)
669*cda5da8dSAndroid Build Coastguard Worker            return 0
670*cda5da8dSAndroid Build Coastguard Worker        def help_add(self):
671*cda5da8dSAndroid Build Coastguard Worker            print("Add profile info from given file to current statistics object.", file=self.stream)
672*cda5da8dSAndroid Build Coastguard Worker
673*cda5da8dSAndroid Build Coastguard Worker        def do_callees(self, line):
674*cda5da8dSAndroid Build Coastguard Worker            return self.generic('print_callees', line)
675*cda5da8dSAndroid Build Coastguard Worker        def help_callees(self):
676*cda5da8dSAndroid Build Coastguard Worker            print("Print callees statistics from the current stat object.", file=self.stream)
677*cda5da8dSAndroid Build Coastguard Worker            self.generic_help()
678*cda5da8dSAndroid Build Coastguard Worker
679*cda5da8dSAndroid Build Coastguard Worker        def do_callers(self, line):
680*cda5da8dSAndroid Build Coastguard Worker            return self.generic('print_callers', line)
681*cda5da8dSAndroid Build Coastguard Worker        def help_callers(self):
682*cda5da8dSAndroid Build Coastguard Worker            print("Print callers statistics from the current stat object.", file=self.stream)
683*cda5da8dSAndroid Build Coastguard Worker            self.generic_help()
684*cda5da8dSAndroid Build Coastguard Worker
685*cda5da8dSAndroid Build Coastguard Worker        def do_EOF(self, line):
686*cda5da8dSAndroid Build Coastguard Worker            print("", file=self.stream)
687*cda5da8dSAndroid Build Coastguard Worker            return 1
688*cda5da8dSAndroid Build Coastguard Worker        def help_EOF(self):
689*cda5da8dSAndroid Build Coastguard Worker            print("Leave the profile browser.", file=self.stream)
690*cda5da8dSAndroid Build Coastguard Worker
691*cda5da8dSAndroid Build Coastguard Worker        def do_quit(self, line):
692*cda5da8dSAndroid Build Coastguard Worker            return 1
693*cda5da8dSAndroid Build Coastguard Worker        def help_quit(self):
694*cda5da8dSAndroid Build Coastguard Worker            print("Leave the profile browser.", file=self.stream)
695*cda5da8dSAndroid Build Coastguard Worker
696*cda5da8dSAndroid Build Coastguard Worker        def do_read(self, line):
697*cda5da8dSAndroid Build Coastguard Worker            if line:
698*cda5da8dSAndroid Build Coastguard Worker                try:
699*cda5da8dSAndroid Build Coastguard Worker                    self.stats = Stats(line)
700*cda5da8dSAndroid Build Coastguard Worker                except OSError as err:
701*cda5da8dSAndroid Build Coastguard Worker                    print(err.args[1], file=self.stream)
702*cda5da8dSAndroid Build Coastguard Worker                    return
703*cda5da8dSAndroid Build Coastguard Worker                except Exception as err:
704*cda5da8dSAndroid Build Coastguard Worker                    print(err.__class__.__name__ + ':', err, file=self.stream)
705*cda5da8dSAndroid Build Coastguard Worker                    return
706*cda5da8dSAndroid Build Coastguard Worker                self.prompt = line + "% "
707*cda5da8dSAndroid Build Coastguard Worker            elif len(self.prompt) > 2:
708*cda5da8dSAndroid Build Coastguard Worker                line = self.prompt[:-2]
709*cda5da8dSAndroid Build Coastguard Worker                self.do_read(line)
710*cda5da8dSAndroid Build Coastguard Worker            else:
711*cda5da8dSAndroid Build Coastguard Worker                print("No statistics object is current -- cannot reload.", file=self.stream)
712*cda5da8dSAndroid Build Coastguard Worker            return 0
713*cda5da8dSAndroid Build Coastguard Worker        def help_read(self):
714*cda5da8dSAndroid Build Coastguard Worker            print("Read in profile data from a specified file.", file=self.stream)
715*cda5da8dSAndroid Build Coastguard Worker            print("Without argument, reload the current file.", file=self.stream)
716*cda5da8dSAndroid Build Coastguard Worker
717*cda5da8dSAndroid Build Coastguard Worker        def do_reverse(self, line):
718*cda5da8dSAndroid Build Coastguard Worker            if self.stats:
719*cda5da8dSAndroid Build Coastguard Worker                self.stats.reverse_order()
720*cda5da8dSAndroid Build Coastguard Worker            else:
721*cda5da8dSAndroid Build Coastguard Worker                print("No statistics object is loaded.", file=self.stream)
722*cda5da8dSAndroid Build Coastguard Worker            return 0
723*cda5da8dSAndroid Build Coastguard Worker        def help_reverse(self):
724*cda5da8dSAndroid Build Coastguard Worker            print("Reverse the sort order of the profiling report.", file=self.stream)
725*cda5da8dSAndroid Build Coastguard Worker
726*cda5da8dSAndroid Build Coastguard Worker        def do_sort(self, line):
727*cda5da8dSAndroid Build Coastguard Worker            if not self.stats:
728*cda5da8dSAndroid Build Coastguard Worker                print("No statistics object is loaded.", file=self.stream)
729*cda5da8dSAndroid Build Coastguard Worker                return
730*cda5da8dSAndroid Build Coastguard Worker            abbrevs = self.stats.get_sort_arg_defs()
731*cda5da8dSAndroid Build Coastguard Worker            if line and all((x in abbrevs) for x in line.split()):
732*cda5da8dSAndroid Build Coastguard Worker                self.stats.sort_stats(*line.split())
733*cda5da8dSAndroid Build Coastguard Worker            else:
734*cda5da8dSAndroid Build Coastguard Worker                print("Valid sort keys (unique prefixes are accepted):", file=self.stream)
735*cda5da8dSAndroid Build Coastguard Worker                for (key, value) in Stats.sort_arg_dict_default.items():
736*cda5da8dSAndroid Build Coastguard Worker                    print("%s -- %s" % (key, value[1]), file=self.stream)
737*cda5da8dSAndroid Build Coastguard Worker            return 0
738*cda5da8dSAndroid Build Coastguard Worker        def help_sort(self):
739*cda5da8dSAndroid Build Coastguard Worker            print("Sort profile data according to specified keys.", file=self.stream)
740*cda5da8dSAndroid Build Coastguard Worker            print("(Typing `sort' without arguments lists valid keys.)", file=self.stream)
741*cda5da8dSAndroid Build Coastguard Worker        def complete_sort(self, text, *args):
742*cda5da8dSAndroid Build Coastguard Worker            return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
743*cda5da8dSAndroid Build Coastguard Worker
744*cda5da8dSAndroid Build Coastguard Worker        def do_stats(self, line):
745*cda5da8dSAndroid Build Coastguard Worker            return self.generic('print_stats', line)
746*cda5da8dSAndroid Build Coastguard Worker        def help_stats(self):
747*cda5da8dSAndroid Build Coastguard Worker            print("Print statistics from the current stat object.", file=self.stream)
748*cda5da8dSAndroid Build Coastguard Worker            self.generic_help()
749*cda5da8dSAndroid Build Coastguard Worker
750*cda5da8dSAndroid Build Coastguard Worker        def do_strip(self, line):
751*cda5da8dSAndroid Build Coastguard Worker            if self.stats:
752*cda5da8dSAndroid Build Coastguard Worker                self.stats.strip_dirs()
753*cda5da8dSAndroid Build Coastguard Worker            else:
754*cda5da8dSAndroid Build Coastguard Worker                print("No statistics object is loaded.", file=self.stream)
755*cda5da8dSAndroid Build Coastguard Worker        def help_strip(self):
756*cda5da8dSAndroid Build Coastguard Worker            print("Strip leading path information from filenames in the report.", file=self.stream)
757*cda5da8dSAndroid Build Coastguard Worker
758*cda5da8dSAndroid Build Coastguard Worker        def help_help(self):
759*cda5da8dSAndroid Build Coastguard Worker            print("Show help for a given command.", file=self.stream)
760*cda5da8dSAndroid Build Coastguard Worker
761*cda5da8dSAndroid Build Coastguard Worker        def postcmd(self, stop, line):
762*cda5da8dSAndroid Build Coastguard Worker            if stop:
763*cda5da8dSAndroid Build Coastguard Worker                return stop
764*cda5da8dSAndroid Build Coastguard Worker            return None
765*cda5da8dSAndroid Build Coastguard Worker
766*cda5da8dSAndroid Build Coastguard Worker    if len(sys.argv) > 1:
767*cda5da8dSAndroid Build Coastguard Worker        initprofile = sys.argv[1]
768*cda5da8dSAndroid Build Coastguard Worker    else:
769*cda5da8dSAndroid Build Coastguard Worker        initprofile = None
770*cda5da8dSAndroid Build Coastguard Worker    try:
771*cda5da8dSAndroid Build Coastguard Worker        browser = ProfileBrowser(initprofile)
772*cda5da8dSAndroid Build Coastguard Worker        for profile in sys.argv[2:]:
773*cda5da8dSAndroid Build Coastguard Worker            browser.do_add(profile)
774*cda5da8dSAndroid Build Coastguard Worker        print("Welcome to the profile statistics browser.", file=browser.stream)
775*cda5da8dSAndroid Build Coastguard Worker        browser.cmdloop()
776*cda5da8dSAndroid Build Coastguard Worker        print("Goodbye.", file=browser.stream)
777*cda5da8dSAndroid Build Coastguard Worker    except KeyboardInterrupt:
778*cda5da8dSAndroid Build Coastguard Worker        pass
779*cda5da8dSAndroid Build Coastguard Worker
780*cda5da8dSAndroid Build Coastguard Worker# That's all, folks.
781