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