1*288bf522SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*288bf522SAndroid Build Coastguard Worker# 3*288bf522SAndroid Build Coastguard Worker# Copyright (C) 2016 The Android Open Source Project 4*288bf522SAndroid Build Coastguard Worker# 5*288bf522SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 6*288bf522SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 7*288bf522SAndroid Build Coastguard Worker# You may obtain a copy of the License at 8*288bf522SAndroid Build Coastguard Worker# 9*288bf522SAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 10*288bf522SAndroid Build Coastguard Worker# 11*288bf522SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 12*288bf522SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 13*288bf522SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*288bf522SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 15*288bf522SAndroid Build Coastguard Worker# limitations under the License. 16*288bf522SAndroid Build Coastguard Worker# 17*288bf522SAndroid Build Coastguard Worker 18*288bf522SAndroid Build Coastguard Worker"""annotate.py: annotate source files based on perf.data. 19*288bf522SAndroid Build Coastguard Worker""" 20*288bf522SAndroid Build Coastguard Worker 21*288bf522SAndroid Build Coastguard Workerimport logging 22*288bf522SAndroid Build Coastguard Workerimport os 23*288bf522SAndroid Build Coastguard Workerimport os.path 24*288bf522SAndroid Build Coastguard Workerimport shutil 25*288bf522SAndroid Build Coastguard Workerfrom texttable import Texttable 26*288bf522SAndroid Build Coastguard Workerfrom typing import Dict, Union 27*288bf522SAndroid Build Coastguard Worker 28*288bf522SAndroid Build Coastguard Workerfrom simpleperf_report_lib import GetReportLib 29*288bf522SAndroid Build Coastguard Workerfrom simpleperf_utils import ( 30*288bf522SAndroid Build Coastguard Worker Addr2Nearestline, BaseArgumentParser, BinaryFinder, extant_dir, flatten_arg_list, is_windows, 31*288bf522SAndroid Build Coastguard Worker log_exit, ReadElf, SourceFileSearcher) 32*288bf522SAndroid Build Coastguard Worker 33*288bf522SAndroid Build Coastguard Worker 34*288bf522SAndroid Build Coastguard Workerclass SourceLine(object): 35*288bf522SAndroid Build Coastguard Worker def __init__(self, file_id, function, line): 36*288bf522SAndroid Build Coastguard Worker self.file = file_id 37*288bf522SAndroid Build Coastguard Worker self.function = function 38*288bf522SAndroid Build Coastguard Worker self.line = line 39*288bf522SAndroid Build Coastguard Worker 40*288bf522SAndroid Build Coastguard Worker @property 41*288bf522SAndroid Build Coastguard Worker def file_key(self): 42*288bf522SAndroid Build Coastguard Worker return self.file 43*288bf522SAndroid Build Coastguard Worker 44*288bf522SAndroid Build Coastguard Worker @property 45*288bf522SAndroid Build Coastguard Worker def function_key(self): 46*288bf522SAndroid Build Coastguard Worker return (self.file, self.function) 47*288bf522SAndroid Build Coastguard Worker 48*288bf522SAndroid Build Coastguard Worker @property 49*288bf522SAndroid Build Coastguard Worker def line_key(self): 50*288bf522SAndroid Build Coastguard Worker return (self.file, self.line) 51*288bf522SAndroid Build Coastguard Worker 52*288bf522SAndroid Build Coastguard Worker 53*288bf522SAndroid Build Coastguard Workerclass Addr2Line(object): 54*288bf522SAndroid Build Coastguard Worker """collect information of how to map [dso_name, vaddr] to [source_file:line]. 55*288bf522SAndroid Build Coastguard Worker """ 56*288bf522SAndroid Build Coastguard Worker 57*288bf522SAndroid Build Coastguard Worker def __init__(self, ndk_path, binary_cache_path, source_dirs): 58*288bf522SAndroid Build Coastguard Worker binary_finder = BinaryFinder(binary_cache_path, ReadElf(ndk_path)) 59*288bf522SAndroid Build Coastguard Worker self.addr2line = Addr2Nearestline(ndk_path, binary_finder, True) 60*288bf522SAndroid Build Coastguard Worker self.source_searcher = SourceFileSearcher(source_dirs) 61*288bf522SAndroid Build Coastguard Worker 62*288bf522SAndroid Build Coastguard Worker def add_addr(self, dso_path: str, build_id: str, func_addr: int, addr: int): 63*288bf522SAndroid Build Coastguard Worker self.addr2line.add_addr(dso_path, build_id, func_addr, addr) 64*288bf522SAndroid Build Coastguard Worker 65*288bf522SAndroid Build Coastguard Worker def convert_addrs_to_lines(self): 66*288bf522SAndroid Build Coastguard Worker self.addr2line.convert_addrs_to_lines(jobs=os.cpu_count()) 67*288bf522SAndroid Build Coastguard Worker 68*288bf522SAndroid Build Coastguard Worker def get_sources(self, dso_path, addr): 69*288bf522SAndroid Build Coastguard Worker dso = self.addr2line.get_dso(dso_path) 70*288bf522SAndroid Build Coastguard Worker if not dso: 71*288bf522SAndroid Build Coastguard Worker return [] 72*288bf522SAndroid Build Coastguard Worker source = self.addr2line.get_addr_source(dso, addr) 73*288bf522SAndroid Build Coastguard Worker if not source: 74*288bf522SAndroid Build Coastguard Worker return [] 75*288bf522SAndroid Build Coastguard Worker result = [] 76*288bf522SAndroid Build Coastguard Worker for (source_file, source_line, function_name) in source: 77*288bf522SAndroid Build Coastguard Worker source_file_path = self.source_searcher.get_real_path(source_file) 78*288bf522SAndroid Build Coastguard Worker if not source_file_path: 79*288bf522SAndroid Build Coastguard Worker source_file_path = source_file 80*288bf522SAndroid Build Coastguard Worker result.append(SourceLine(source_file_path, function_name, source_line)) 81*288bf522SAndroid Build Coastguard Worker return result 82*288bf522SAndroid Build Coastguard Worker 83*288bf522SAndroid Build Coastguard Worker 84*288bf522SAndroid Build Coastguard Workerclass Period(object): 85*288bf522SAndroid Build Coastguard Worker """event count information. It can be used to represent event count 86*288bf522SAndroid Build Coastguard Worker of a line, a function, a source file, or a binary. It contains two 87*288bf522SAndroid Build Coastguard Worker parts: period and acc_period. 88*288bf522SAndroid Build Coastguard Worker When used for a line, period is the event count occurred when running 89*288bf522SAndroid Build Coastguard Worker that line, acc_period is the accumulated event count occurred when 90*288bf522SAndroid Build Coastguard Worker running that line and functions called by that line. Same thing applies 91*288bf522SAndroid Build Coastguard Worker when it is used for a function, a source file, or a binary. 92*288bf522SAndroid Build Coastguard Worker """ 93*288bf522SAndroid Build Coastguard Worker 94*288bf522SAndroid Build Coastguard Worker def __init__(self, period=0, acc_period=0): 95*288bf522SAndroid Build Coastguard Worker self.period = period 96*288bf522SAndroid Build Coastguard Worker self.acc_period = acc_period 97*288bf522SAndroid Build Coastguard Worker 98*288bf522SAndroid Build Coastguard Worker def __iadd__(self, other): 99*288bf522SAndroid Build Coastguard Worker self.period += other.period 100*288bf522SAndroid Build Coastguard Worker self.acc_period += other.acc_period 101*288bf522SAndroid Build Coastguard Worker return self 102*288bf522SAndroid Build Coastguard Worker 103*288bf522SAndroid Build Coastguard Worker 104*288bf522SAndroid Build Coastguard Workerclass DsoPeriod(object): 105*288bf522SAndroid Build Coastguard Worker """Period for each shared library""" 106*288bf522SAndroid Build Coastguard Worker 107*288bf522SAndroid Build Coastguard Worker def __init__(self, dso_name): 108*288bf522SAndroid Build Coastguard Worker self.dso_name = dso_name 109*288bf522SAndroid Build Coastguard Worker self.period = Period() 110*288bf522SAndroid Build Coastguard Worker 111*288bf522SAndroid Build Coastguard Worker def add_period(self, period): 112*288bf522SAndroid Build Coastguard Worker self.period += period 113*288bf522SAndroid Build Coastguard Worker 114*288bf522SAndroid Build Coastguard Worker 115*288bf522SAndroid Build Coastguard Workerclass FilePeriod(object): 116*288bf522SAndroid Build Coastguard Worker """Period for each source file""" 117*288bf522SAndroid Build Coastguard Worker 118*288bf522SAndroid Build Coastguard Worker def __init__(self, file_id): 119*288bf522SAndroid Build Coastguard Worker self.file = file_id 120*288bf522SAndroid Build Coastguard Worker self.period = Period() 121*288bf522SAndroid Build Coastguard Worker # Period for each line in the file. 122*288bf522SAndroid Build Coastguard Worker self.line_dict = {} 123*288bf522SAndroid Build Coastguard Worker # Period for each function in the source file. 124*288bf522SAndroid Build Coastguard Worker self.function_dict = {} 125*288bf522SAndroid Build Coastguard Worker 126*288bf522SAndroid Build Coastguard Worker def add_period(self, period): 127*288bf522SAndroid Build Coastguard Worker self.period += period 128*288bf522SAndroid Build Coastguard Worker 129*288bf522SAndroid Build Coastguard Worker def add_line_period(self, line, period): 130*288bf522SAndroid Build Coastguard Worker a = self.line_dict.get(line) 131*288bf522SAndroid Build Coastguard Worker if a is None: 132*288bf522SAndroid Build Coastguard Worker self.line_dict[line] = a = Period() 133*288bf522SAndroid Build Coastguard Worker a += period 134*288bf522SAndroid Build Coastguard Worker 135*288bf522SAndroid Build Coastguard Worker def add_function_period(self, function_name, function_start_line, period): 136*288bf522SAndroid Build Coastguard Worker a = self.function_dict.get(function_name) 137*288bf522SAndroid Build Coastguard Worker if not a: 138*288bf522SAndroid Build Coastguard Worker if function_start_line is None: 139*288bf522SAndroid Build Coastguard Worker function_start_line = -1 140*288bf522SAndroid Build Coastguard Worker self.function_dict[function_name] = a = [function_start_line, Period()] 141*288bf522SAndroid Build Coastguard Worker a[1] += period 142*288bf522SAndroid Build Coastguard Worker 143*288bf522SAndroid Build Coastguard Worker 144*288bf522SAndroid Build Coastguard Workerclass SourceFileAnnotator(object): 145*288bf522SAndroid Build Coastguard Worker """group code for annotating source files""" 146*288bf522SAndroid Build Coastguard Worker 147*288bf522SAndroid Build Coastguard Worker def __init__(self, config): 148*288bf522SAndroid Build Coastguard Worker # check config variables 149*288bf522SAndroid Build Coastguard Worker config_names = ['perf_data_list', 'source_dirs', 'dso_filters', 'ndk_path'] 150*288bf522SAndroid Build Coastguard Worker for name in config_names: 151*288bf522SAndroid Build Coastguard Worker if name not in config: 152*288bf522SAndroid Build Coastguard Worker log_exit('config [%s] is missing' % name) 153*288bf522SAndroid Build Coastguard Worker symfs_dir = 'binary_cache' 154*288bf522SAndroid Build Coastguard Worker if not os.path.isdir(symfs_dir): 155*288bf522SAndroid Build Coastguard Worker symfs_dir = None 156*288bf522SAndroid Build Coastguard Worker kallsyms = 'binary_cache/kallsyms' 157*288bf522SAndroid Build Coastguard Worker if not os.path.isfile(kallsyms): 158*288bf522SAndroid Build Coastguard Worker kallsyms = None 159*288bf522SAndroid Build Coastguard Worker 160*288bf522SAndroid Build Coastguard Worker # init member variables 161*288bf522SAndroid Build Coastguard Worker self.config = config 162*288bf522SAndroid Build Coastguard Worker self.symfs_dir = symfs_dir 163*288bf522SAndroid Build Coastguard Worker self.kallsyms = kallsyms 164*288bf522SAndroid Build Coastguard Worker self.dso_filter = set(config['dso_filters']) if config.get('dso_filters') else None 165*288bf522SAndroid Build Coastguard Worker 166*288bf522SAndroid Build Coastguard Worker config['annotate_dest_dir'] = 'annotated_files' 167*288bf522SAndroid Build Coastguard Worker output_dir = config['annotate_dest_dir'] 168*288bf522SAndroid Build Coastguard Worker if os.path.isdir(output_dir): 169*288bf522SAndroid Build Coastguard Worker shutil.rmtree(output_dir) 170*288bf522SAndroid Build Coastguard Worker os.makedirs(output_dir) 171*288bf522SAndroid Build Coastguard Worker 172*288bf522SAndroid Build Coastguard Worker self.addr2line = Addr2Line(self.config['ndk_path'], symfs_dir, config.get('source_dirs')) 173*288bf522SAndroid Build Coastguard Worker self.period = 0 174*288bf522SAndroid Build Coastguard Worker self.dso_periods = {} 175*288bf522SAndroid Build Coastguard Worker self.file_periods = {} 176*288bf522SAndroid Build Coastguard Worker 177*288bf522SAndroid Build Coastguard Worker def annotate(self): 178*288bf522SAndroid Build Coastguard Worker self._collect_addrs() 179*288bf522SAndroid Build Coastguard Worker self._convert_addrs_to_lines() 180*288bf522SAndroid Build Coastguard Worker self._generate_periods() 181*288bf522SAndroid Build Coastguard Worker self._write_summary() 182*288bf522SAndroid Build Coastguard Worker self._annotate_files() 183*288bf522SAndroid Build Coastguard Worker 184*288bf522SAndroid Build Coastguard Worker def _collect_addrs(self): 185*288bf522SAndroid Build Coastguard Worker """Read perf.data, collect all addresses we need to convert to 186*288bf522SAndroid Build Coastguard Worker source file:line. 187*288bf522SAndroid Build Coastguard Worker """ 188*288bf522SAndroid Build Coastguard Worker for perf_data in self.config['perf_data_list']: 189*288bf522SAndroid Build Coastguard Worker lib = GetReportLib(perf_data) 190*288bf522SAndroid Build Coastguard Worker if self.symfs_dir: 191*288bf522SAndroid Build Coastguard Worker lib.SetSymfs(self.symfs_dir) 192*288bf522SAndroid Build Coastguard Worker if self.kallsyms: 193*288bf522SAndroid Build Coastguard Worker lib.SetKallsymsFile(self.kallsyms) 194*288bf522SAndroid Build Coastguard Worker lib.SetReportOptions(self.config['report_lib_options']) 195*288bf522SAndroid Build Coastguard Worker while True: 196*288bf522SAndroid Build Coastguard Worker sample = lib.GetNextSample() 197*288bf522SAndroid Build Coastguard Worker if sample is None: 198*288bf522SAndroid Build Coastguard Worker lib.Close() 199*288bf522SAndroid Build Coastguard Worker break 200*288bf522SAndroid Build Coastguard Worker symbols = [] 201*288bf522SAndroid Build Coastguard Worker symbols.append(lib.GetSymbolOfCurrentSample()) 202*288bf522SAndroid Build Coastguard Worker callchain = lib.GetCallChainOfCurrentSample() 203*288bf522SAndroid Build Coastguard Worker for i in range(callchain.nr): 204*288bf522SAndroid Build Coastguard Worker symbols.append(callchain.entries[i].symbol) 205*288bf522SAndroid Build Coastguard Worker for symbol in symbols: 206*288bf522SAndroid Build Coastguard Worker if self._filter_symbol(symbol): 207*288bf522SAndroid Build Coastguard Worker build_id = lib.GetBuildIdForPath(symbol.dso_name) 208*288bf522SAndroid Build Coastguard Worker self.addr2line.add_addr(symbol.dso_name, build_id, symbol.symbol_addr, 209*288bf522SAndroid Build Coastguard Worker symbol.vaddr_in_file) 210*288bf522SAndroid Build Coastguard Worker self.addr2line.add_addr(symbol.dso_name, build_id, symbol.symbol_addr, 211*288bf522SAndroid Build Coastguard Worker symbol.symbol_addr) 212*288bf522SAndroid Build Coastguard Worker 213*288bf522SAndroid Build Coastguard Worker def _filter_symbol(self, symbol): 214*288bf522SAndroid Build Coastguard Worker if not self.dso_filter or symbol.dso_name in self.dso_filter: 215*288bf522SAndroid Build Coastguard Worker return True 216*288bf522SAndroid Build Coastguard Worker return False 217*288bf522SAndroid Build Coastguard Worker 218*288bf522SAndroid Build Coastguard Worker def _convert_addrs_to_lines(self): 219*288bf522SAndroid Build Coastguard Worker self.addr2line.convert_addrs_to_lines() 220*288bf522SAndroid Build Coastguard Worker 221*288bf522SAndroid Build Coastguard Worker def _generate_periods(self): 222*288bf522SAndroid Build Coastguard Worker """read perf.data, collect Period for all types: 223*288bf522SAndroid Build Coastguard Worker binaries, source files, functions, lines. 224*288bf522SAndroid Build Coastguard Worker """ 225*288bf522SAndroid Build Coastguard Worker for perf_data in self.config['perf_data_list']: 226*288bf522SAndroid Build Coastguard Worker lib = GetReportLib(perf_data) 227*288bf522SAndroid Build Coastguard Worker if self.symfs_dir: 228*288bf522SAndroid Build Coastguard Worker lib.SetSymfs(self.symfs_dir) 229*288bf522SAndroid Build Coastguard Worker if self.kallsyms: 230*288bf522SAndroid Build Coastguard Worker lib.SetKallsymsFile(self.kallsyms) 231*288bf522SAndroid Build Coastguard Worker lib.SetReportOptions(self.config['report_lib_options']) 232*288bf522SAndroid Build Coastguard Worker while True: 233*288bf522SAndroid Build Coastguard Worker sample = lib.GetNextSample() 234*288bf522SAndroid Build Coastguard Worker if sample is None: 235*288bf522SAndroid Build Coastguard Worker lib.Close() 236*288bf522SAndroid Build Coastguard Worker break 237*288bf522SAndroid Build Coastguard Worker self._generate_periods_for_sample(lib, sample) 238*288bf522SAndroid Build Coastguard Worker 239*288bf522SAndroid Build Coastguard Worker def _generate_periods_for_sample(self, lib, sample): 240*288bf522SAndroid Build Coastguard Worker symbols = [] 241*288bf522SAndroid Build Coastguard Worker symbols.append(lib.GetSymbolOfCurrentSample()) 242*288bf522SAndroid Build Coastguard Worker callchain = lib.GetCallChainOfCurrentSample() 243*288bf522SAndroid Build Coastguard Worker for i in range(callchain.nr): 244*288bf522SAndroid Build Coastguard Worker symbols.append(callchain.entries[i].symbol) 245*288bf522SAndroid Build Coastguard Worker # Each sample has a callchain, but its period is only used once 246*288bf522SAndroid Build Coastguard Worker # to add period for each function/source_line/source_file/binary. 247*288bf522SAndroid Build Coastguard Worker # For example, if more than one entry in the callchain hits a 248*288bf522SAndroid Build Coastguard Worker # function, the event count of that function is only increased once. 249*288bf522SAndroid Build Coastguard Worker # Otherwise, we may get periods > 100%. 250*288bf522SAndroid Build Coastguard Worker is_sample_used = False 251*288bf522SAndroid Build Coastguard Worker used_dso_dict = {} 252*288bf522SAndroid Build Coastguard Worker used_file_dict = {} 253*288bf522SAndroid Build Coastguard Worker used_function_dict = {} 254*288bf522SAndroid Build Coastguard Worker used_line_dict = {} 255*288bf522SAndroid Build Coastguard Worker period = Period(sample.period, sample.period) 256*288bf522SAndroid Build Coastguard Worker for j, symbol in enumerate(symbols): 257*288bf522SAndroid Build Coastguard Worker if j == 1: 258*288bf522SAndroid Build Coastguard Worker period = Period(0, sample.period) 259*288bf522SAndroid Build Coastguard Worker if not self._filter_symbol(symbol): 260*288bf522SAndroid Build Coastguard Worker continue 261*288bf522SAndroid Build Coastguard Worker is_sample_used = True 262*288bf522SAndroid Build Coastguard Worker # Add period to dso. 263*288bf522SAndroid Build Coastguard Worker self._add_dso_period(symbol.dso_name, period, used_dso_dict) 264*288bf522SAndroid Build Coastguard Worker # Add period to source file. 265*288bf522SAndroid Build Coastguard Worker sources = self.addr2line.get_sources(symbol.dso_name, symbol.vaddr_in_file) 266*288bf522SAndroid Build Coastguard Worker for source in sources: 267*288bf522SAndroid Build Coastguard Worker if source.file: 268*288bf522SAndroid Build Coastguard Worker self._add_file_period(source, period, used_file_dict) 269*288bf522SAndroid Build Coastguard Worker # Add period to line. 270*288bf522SAndroid Build Coastguard Worker if source.line: 271*288bf522SAndroid Build Coastguard Worker self._add_line_period(source, period, used_line_dict) 272*288bf522SAndroid Build Coastguard Worker # Add period to function. 273*288bf522SAndroid Build Coastguard Worker sources = self.addr2line.get_sources(symbol.dso_name, symbol.symbol_addr) 274*288bf522SAndroid Build Coastguard Worker for source in sources: 275*288bf522SAndroid Build Coastguard Worker if source.file: 276*288bf522SAndroid Build Coastguard Worker self._add_file_period(source, period, used_file_dict) 277*288bf522SAndroid Build Coastguard Worker if source.function: 278*288bf522SAndroid Build Coastguard Worker self._add_function_period(source, period, used_function_dict) 279*288bf522SAndroid Build Coastguard Worker 280*288bf522SAndroid Build Coastguard Worker if is_sample_used: 281*288bf522SAndroid Build Coastguard Worker self.period += sample.period 282*288bf522SAndroid Build Coastguard Worker 283*288bf522SAndroid Build Coastguard Worker def _add_dso_period(self, dso_name: str, period: Period, used_dso_dict: Dict[str, bool]): 284*288bf522SAndroid Build Coastguard Worker if dso_name not in used_dso_dict: 285*288bf522SAndroid Build Coastguard Worker used_dso_dict[dso_name] = True 286*288bf522SAndroid Build Coastguard Worker dso_period = self.dso_periods.get(dso_name) 287*288bf522SAndroid Build Coastguard Worker if dso_period is None: 288*288bf522SAndroid Build Coastguard Worker dso_period = self.dso_periods[dso_name] = DsoPeriod(dso_name) 289*288bf522SAndroid Build Coastguard Worker dso_period.add_period(period) 290*288bf522SAndroid Build Coastguard Worker 291*288bf522SAndroid Build Coastguard Worker def _add_file_period(self, source, period, used_file_dict): 292*288bf522SAndroid Build Coastguard Worker if source.file_key not in used_file_dict: 293*288bf522SAndroid Build Coastguard Worker used_file_dict[source.file_key] = True 294*288bf522SAndroid Build Coastguard Worker file_period = self.file_periods.get(source.file) 295*288bf522SAndroid Build Coastguard Worker if file_period is None: 296*288bf522SAndroid Build Coastguard Worker file_period = self.file_periods[source.file] = FilePeriod(source.file) 297*288bf522SAndroid Build Coastguard Worker file_period.add_period(period) 298*288bf522SAndroid Build Coastguard Worker 299*288bf522SAndroid Build Coastguard Worker def _add_line_period(self, source, period, used_line_dict): 300*288bf522SAndroid Build Coastguard Worker if source.line_key not in used_line_dict: 301*288bf522SAndroid Build Coastguard Worker used_line_dict[source.line_key] = True 302*288bf522SAndroid Build Coastguard Worker file_period = self.file_periods[source.file] 303*288bf522SAndroid Build Coastguard Worker file_period.add_line_period(source.line, period) 304*288bf522SAndroid Build Coastguard Worker 305*288bf522SAndroid Build Coastguard Worker def _add_function_period(self, source, period, used_function_dict): 306*288bf522SAndroid Build Coastguard Worker if source.function_key not in used_function_dict: 307*288bf522SAndroid Build Coastguard Worker used_function_dict[source.function_key] = True 308*288bf522SAndroid Build Coastguard Worker file_period = self.file_periods[source.file] 309*288bf522SAndroid Build Coastguard Worker file_period.add_function_period(source.function, source.line, period) 310*288bf522SAndroid Build Coastguard Worker 311*288bf522SAndroid Build Coastguard Worker def _write_summary(self): 312*288bf522SAndroid Build Coastguard Worker summary = os.path.join(self.config['annotate_dest_dir'], 'summary') 313*288bf522SAndroid Build Coastguard Worker with open(summary, 'w') as f: 314*288bf522SAndroid Build Coastguard Worker f.write('total period: %d\n\n' % self.period) 315*288bf522SAndroid Build Coastguard Worker self._write_dso_summary(f) 316*288bf522SAndroid Build Coastguard Worker self._write_file_summary(f) 317*288bf522SAndroid Build Coastguard Worker 318*288bf522SAndroid Build Coastguard Worker file_periods = sorted(self.file_periods.values(), 319*288bf522SAndroid Build Coastguard Worker key=lambda x: x.period.acc_period, reverse=True) 320*288bf522SAndroid Build Coastguard Worker for file_period in file_periods: 321*288bf522SAndroid Build Coastguard Worker self._write_function_line_summary(f, file_period) 322*288bf522SAndroid Build Coastguard Worker 323*288bf522SAndroid Build Coastguard Worker def _write_dso_summary(self, summary_fh): 324*288bf522SAndroid Build Coastguard Worker dso_periods = sorted(self.dso_periods.values(), 325*288bf522SAndroid Build Coastguard Worker key=lambda x: x.period.acc_period, reverse=True) 326*288bf522SAndroid Build Coastguard Worker table = Texttable(max_width=self.config['summary_width']) 327*288bf522SAndroid Build Coastguard Worker table.set_cols_align(['l', 'l', 'l']) 328*288bf522SAndroid Build Coastguard Worker table.add_row(['Total', 'Self', 'DSO']) 329*288bf522SAndroid Build Coastguard Worker for dso_period in dso_periods: 330*288bf522SAndroid Build Coastguard Worker total_str = self._get_period_str(dso_period.period.acc_period) 331*288bf522SAndroid Build Coastguard Worker self_str = self._get_period_str(dso_period.period.period) 332*288bf522SAndroid Build Coastguard Worker table.add_row([total_str, self_str, dso_period.dso_name]) 333*288bf522SAndroid Build Coastguard Worker print(table.draw(), file=summary_fh) 334*288bf522SAndroid Build Coastguard Worker print(file=summary_fh) 335*288bf522SAndroid Build Coastguard Worker 336*288bf522SAndroid Build Coastguard Worker def _write_file_summary(self, summary_fh): 337*288bf522SAndroid Build Coastguard Worker file_periods = sorted(self.file_periods.values(), 338*288bf522SAndroid Build Coastguard Worker key=lambda x: x.period.acc_period, reverse=True) 339*288bf522SAndroid Build Coastguard Worker table = Texttable(max_width=self.config['summary_width']) 340*288bf522SAndroid Build Coastguard Worker table.set_cols_align(['l', 'l', 'l']) 341*288bf522SAndroid Build Coastguard Worker table.add_row(['Total', 'Self', 'Source File']) 342*288bf522SAndroid Build Coastguard Worker for file_period in file_periods: 343*288bf522SAndroid Build Coastguard Worker total_str = self._get_period_str(file_period.period.acc_period) 344*288bf522SAndroid Build Coastguard Worker self_str = self._get_period_str(file_period.period.period) 345*288bf522SAndroid Build Coastguard Worker table.add_row([total_str, self_str, file_period.file]) 346*288bf522SAndroid Build Coastguard Worker print(table.draw(), file=summary_fh) 347*288bf522SAndroid Build Coastguard Worker print(file=summary_fh) 348*288bf522SAndroid Build Coastguard Worker 349*288bf522SAndroid Build Coastguard Worker def _write_function_line_summary(self, summary_fh, file_period: FilePeriod): 350*288bf522SAndroid Build Coastguard Worker table = Texttable(max_width=self.config['summary_width']) 351*288bf522SAndroid Build Coastguard Worker table.set_cols_align(['l', 'l', 'l']) 352*288bf522SAndroid Build Coastguard Worker table.add_row(['Total', 'Self', 'Function/Line in ' + file_period.file]) 353*288bf522SAndroid Build Coastguard Worker values = [] 354*288bf522SAndroid Build Coastguard Worker for func_name in file_period.function_dict.keys(): 355*288bf522SAndroid Build Coastguard Worker func_start_line, period = file_period.function_dict[func_name] 356*288bf522SAndroid Build Coastguard Worker values.append((func_name, func_start_line, period)) 357*288bf522SAndroid Build Coastguard Worker values.sort(key=lambda x: x[2].acc_period, reverse=True) 358*288bf522SAndroid Build Coastguard Worker for func_name, func_start_line, period in values: 359*288bf522SAndroid Build Coastguard Worker total_str = self._get_period_str(period.acc_period) 360*288bf522SAndroid Build Coastguard Worker self_str = self._get_period_str(period.period) 361*288bf522SAndroid Build Coastguard Worker name = func_name + ' (line %d)' % func_start_line 362*288bf522SAndroid Build Coastguard Worker table.add_row([total_str, self_str, name]) 363*288bf522SAndroid Build Coastguard Worker for line in sorted(file_period.line_dict.keys()): 364*288bf522SAndroid Build Coastguard Worker period = file_period.line_dict[line] 365*288bf522SAndroid Build Coastguard Worker total_str = self._get_period_str(period.acc_period) 366*288bf522SAndroid Build Coastguard Worker self_str = self._get_period_str(period.period) 367*288bf522SAndroid Build Coastguard Worker name = 'line %d' % line 368*288bf522SAndroid Build Coastguard Worker table.add_row([total_str, self_str, name]) 369*288bf522SAndroid Build Coastguard Worker 370*288bf522SAndroid Build Coastguard Worker print(table.draw(), file=summary_fh) 371*288bf522SAndroid Build Coastguard Worker print(file=summary_fh) 372*288bf522SAndroid Build Coastguard Worker 373*288bf522SAndroid Build Coastguard Worker def _get_period_str(self, period: Union[Period, int]) -> str: 374*288bf522SAndroid Build Coastguard Worker if isinstance(period, Period): 375*288bf522SAndroid Build Coastguard Worker return 'Total %s, Self %s' % ( 376*288bf522SAndroid Build Coastguard Worker self._get_period_str(period.acc_period), 377*288bf522SAndroid Build Coastguard Worker self._get_period_str(period.period)) 378*288bf522SAndroid Build Coastguard Worker if self.config['raw_period'] or self.period == 0: 379*288bf522SAndroid Build Coastguard Worker return str(period) 380*288bf522SAndroid Build Coastguard Worker return '%.2f%%' % (100.0 * period / self.period) 381*288bf522SAndroid Build Coastguard Worker 382*288bf522SAndroid Build Coastguard Worker def _annotate_files(self): 383*288bf522SAndroid Build Coastguard Worker """Annotate Source files: add acc_period/period for each source file. 384*288bf522SAndroid Build Coastguard Worker 1. Annotate java source files, which have $JAVA_SRC_ROOT prefix. 385*288bf522SAndroid Build Coastguard Worker 2. Annotate c++ source files. 386*288bf522SAndroid Build Coastguard Worker """ 387*288bf522SAndroid Build Coastguard Worker dest_dir = self.config['annotate_dest_dir'] 388*288bf522SAndroid Build Coastguard Worker for key in self.file_periods: 389*288bf522SAndroid Build Coastguard Worker from_path = key 390*288bf522SAndroid Build Coastguard Worker if not os.path.isfile(from_path): 391*288bf522SAndroid Build Coastguard Worker logging.warning("can't find source file for path %s" % from_path) 392*288bf522SAndroid Build Coastguard Worker continue 393*288bf522SAndroid Build Coastguard Worker if from_path.startswith('/'): 394*288bf522SAndroid Build Coastguard Worker to_path = os.path.join(dest_dir, from_path[1:]) 395*288bf522SAndroid Build Coastguard Worker elif is_windows() and ':\\' in from_path: 396*288bf522SAndroid Build Coastguard Worker to_path = os.path.join(dest_dir, from_path.replace(':\\', os.sep)) 397*288bf522SAndroid Build Coastguard Worker else: 398*288bf522SAndroid Build Coastguard Worker to_path = os.path.join(dest_dir, from_path) 399*288bf522SAndroid Build Coastguard Worker is_java = from_path.endswith('.java') 400*288bf522SAndroid Build Coastguard Worker self._annotate_file(from_path, to_path, self.file_periods[key], is_java) 401*288bf522SAndroid Build Coastguard Worker 402*288bf522SAndroid Build Coastguard Worker def _annotate_file(self, from_path, to_path, file_period, is_java): 403*288bf522SAndroid Build Coastguard Worker """Annotate a source file. 404*288bf522SAndroid Build Coastguard Worker 405*288bf522SAndroid Build Coastguard Worker Annotate a source file in three steps: 406*288bf522SAndroid Build Coastguard Worker 1. In the first line, show periods of this file. 407*288bf522SAndroid Build Coastguard Worker 2. For each function, show periods of this function. 408*288bf522SAndroid Build Coastguard Worker 3. For each line not hitting the same line as functions, show 409*288bf522SAndroid Build Coastguard Worker line periods. 410*288bf522SAndroid Build Coastguard Worker """ 411*288bf522SAndroid Build Coastguard Worker logging.info('annotate file %s' % from_path) 412*288bf522SAndroid Build Coastguard Worker with open(from_path, 'r') as rf: 413*288bf522SAndroid Build Coastguard Worker lines = rf.readlines() 414*288bf522SAndroid Build Coastguard Worker 415*288bf522SAndroid Build Coastguard Worker annotates = {} 416*288bf522SAndroid Build Coastguard Worker for line in file_period.line_dict.keys(): 417*288bf522SAndroid Build Coastguard Worker annotates[line] = self._get_period_str(file_period.line_dict[line]) 418*288bf522SAndroid Build Coastguard Worker for func_name in file_period.function_dict.keys(): 419*288bf522SAndroid Build Coastguard Worker func_start_line, period = file_period.function_dict[func_name] 420*288bf522SAndroid Build Coastguard Worker if func_start_line == -1: 421*288bf522SAndroid Build Coastguard Worker continue 422*288bf522SAndroid Build Coastguard Worker line = func_start_line - 1 if is_java else func_start_line 423*288bf522SAndroid Build Coastguard Worker annotates[line] = '[func] ' + self._get_period_str(period) 424*288bf522SAndroid Build Coastguard Worker annotates[1] = '[file] ' + self._get_period_str(file_period.period) 425*288bf522SAndroid Build Coastguard Worker 426*288bf522SAndroid Build Coastguard Worker max_annotate_cols = 0 427*288bf522SAndroid Build Coastguard Worker for key in annotates: 428*288bf522SAndroid Build Coastguard Worker max_annotate_cols = max(max_annotate_cols, len(annotates[key])) 429*288bf522SAndroid Build Coastguard Worker 430*288bf522SAndroid Build Coastguard Worker empty_annotate = ' ' * (max_annotate_cols + 6) 431*288bf522SAndroid Build Coastguard Worker 432*288bf522SAndroid Build Coastguard Worker dirname = os.path.dirname(to_path) 433*288bf522SAndroid Build Coastguard Worker if not os.path.isdir(dirname): 434*288bf522SAndroid Build Coastguard Worker os.makedirs(dirname) 435*288bf522SAndroid Build Coastguard Worker with open(to_path, 'w') as wf: 436*288bf522SAndroid Build Coastguard Worker for line in range(1, len(lines) + 1): 437*288bf522SAndroid Build Coastguard Worker annotate = annotates.get(line) 438*288bf522SAndroid Build Coastguard Worker if annotate is None: 439*288bf522SAndroid Build Coastguard Worker if not lines[line-1].strip(): 440*288bf522SAndroid Build Coastguard Worker annotate = '' 441*288bf522SAndroid Build Coastguard Worker else: 442*288bf522SAndroid Build Coastguard Worker annotate = empty_annotate 443*288bf522SAndroid Build Coastguard Worker else: 444*288bf522SAndroid Build Coastguard Worker annotate = '/* ' + annotate + ( 445*288bf522SAndroid Build Coastguard Worker ' ' * (max_annotate_cols - len(annotate))) + ' */' 446*288bf522SAndroid Build Coastguard Worker wf.write(annotate) 447*288bf522SAndroid Build Coastguard Worker wf.write(lines[line-1]) 448*288bf522SAndroid Build Coastguard Worker 449*288bf522SAndroid Build Coastguard Worker 450*288bf522SAndroid Build Coastguard Workerdef main(): 451*288bf522SAndroid Build Coastguard Worker parser = BaseArgumentParser(description=""" 452*288bf522SAndroid Build Coastguard Worker Annotate source files based on profiling data. It reads line information from binary_cache 453*288bf522SAndroid Build Coastguard Worker generated by app_profiler.py or binary_cache_builder.py, and generate annotated source 454*288bf522SAndroid Build Coastguard Worker files in annotated_files directory.""") 455*288bf522SAndroid Build Coastguard Worker parser.add_argument('-i', '--perf_data_list', nargs='+', action='append', help=""" 456*288bf522SAndroid Build Coastguard Worker The paths of profiling data. Default is perf.data.""") 457*288bf522SAndroid Build Coastguard Worker parser.add_argument('-s', '--source_dirs', type=extant_dir, nargs='+', action='append', help=""" 458*288bf522SAndroid Build Coastguard Worker Directories to find source files.""") 459*288bf522SAndroid Build Coastguard Worker parser.add_argument('--ndk_path', type=extant_dir, help='Set the path of a ndk release.') 460*288bf522SAndroid Build Coastguard Worker parser.add_argument('--raw-period', action='store_true', 461*288bf522SAndroid Build Coastguard Worker help='show raw period instead of percentage') 462*288bf522SAndroid Build Coastguard Worker parser.add_argument('--summary-width', type=int, default=80, help='max width of summary file') 463*288bf522SAndroid Build Coastguard Worker sample_filter_group = parser.add_argument_group('Sample filter options') 464*288bf522SAndroid Build Coastguard Worker sample_filter_group.add_argument('--dso', nargs='+', action='append', help=""" 465*288bf522SAndroid Build Coastguard Worker Use samples only in selected binaries.""") 466*288bf522SAndroid Build Coastguard Worker parser.add_report_lib_options(sample_filter_group=sample_filter_group) 467*288bf522SAndroid Build Coastguard Worker 468*288bf522SAndroid Build Coastguard Worker args = parser.parse_args() 469*288bf522SAndroid Build Coastguard Worker config = {} 470*288bf522SAndroid Build Coastguard Worker config['perf_data_list'] = flatten_arg_list(args.perf_data_list) 471*288bf522SAndroid Build Coastguard Worker if not config['perf_data_list']: 472*288bf522SAndroid Build Coastguard Worker config['perf_data_list'].append('perf.data') 473*288bf522SAndroid Build Coastguard Worker config['source_dirs'] = flatten_arg_list(args.source_dirs) 474*288bf522SAndroid Build Coastguard Worker config['dso_filters'] = flatten_arg_list(args.dso) 475*288bf522SAndroid Build Coastguard Worker config['ndk_path'] = args.ndk_path 476*288bf522SAndroid Build Coastguard Worker config['raw_period'] = args.raw_period 477*288bf522SAndroid Build Coastguard Worker config['summary_width'] = args.summary_width 478*288bf522SAndroid Build Coastguard Worker config['report_lib_options'] = args.report_lib_options 479*288bf522SAndroid Build Coastguard Worker 480*288bf522SAndroid Build Coastguard Worker annotator = SourceFileAnnotator(config) 481*288bf522SAndroid Build Coastguard Worker annotator.annotate() 482*288bf522SAndroid Build Coastguard Worker logging.info('annotate finish successfully, please check result in annotated_files/.') 483*288bf522SAndroid Build Coastguard Worker 484*288bf522SAndroid Build Coastguard Worker 485*288bf522SAndroid Build Coastguard Workerif __name__ == '__main__': 486*288bf522SAndroid Build Coastguard Worker main() 487