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