blob: 27d67a8fc39196e97639557e172b4605f088cf49 [file] [log] [blame]
Yabin Cuib44c3362021-06-01 13:14:05 -07001#!/usr/bin/env python3
Yabin Cui6e25cdb2017-01-09 13:45:27 -08002#
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 Cuifd4adae2021-10-25 13:55:01 -070021import logging
Yabin Cui6e25cdb2017-01-09 13:45:27 -080022import os
23import os.path
24import shutil
Yabin Cui6e25cdb2017-01-09 13:45:27 -080025
Yabin Cui58814c62018-08-07 12:12:31 -070026from simpleperf_report_lib import ReportLib
Yabin Cuib44c3362021-06-01 13:14:05 -070027from simpleperf_utils import (
Yabin Cuifd4adae2021-10-25 13:55:01 -070028 Addr2Nearestline, BaseArgumentParser, BinaryFinder, extant_dir, flatten_arg_list, is_windows,
29 log_exit, ReadElf, SourceFileSearcher)
Yabin Cuib44c3362021-06-01 13:14:05 -070030
Yabin Cui6e25cdb2017-01-09 13:45:27 -080031
Yabin Cui5b01fe62017-03-22 11:52:00 -070032class SourceLine(object):
Yabin Cui58814c62018-08-07 12:12:31 -070033 def __init__(self, file_id, function, line):
34 self.file = file_id
Yabin Cui5b01fe62017-03-22 11:52:00 -070035 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 Cui6e25cdb2017-01-09 13:45:27 -080051class Addr2Line(object):
Yabin Cui58814c62018-08-07 12:12:31 -070052 """collect information of how to map [dso_name, vaddr] to [source_file:line].
Yabin Cui6e25cdb2017-01-09 13:45:27 -080053 """
Yabin Cuib44c3362021-06-01 13:14:05 -070054
Yabin Cui58814c62018-08-07 12:12:31 -070055 def __init__(self, ndk_path, binary_cache_path, source_dirs):
Yabin Cuib44c3362021-06-01 13:14:05 -070056 binary_finder = BinaryFinder(binary_cache_path, ReadElf(ndk_path))
57 self.addr2line = Addr2Nearestline(ndk_path, binary_finder, True)
Yabin Cui58814c62018-08-07 12:12:31 -070058 self.source_searcher = SourceFileSearcher(source_dirs)
Yabin Cui6e25cdb2017-01-09 13:45:27 -080059
Yabin Cuib44c3362021-06-01 13:14:05 -070060 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 Cui6e25cdb2017-01-09 13:45:27 -080062
63 def convert_addrs_to_lines(self):
Yabin Cui9002fe02021-07-15 15:32:46 -070064 self.addr2line.convert_addrs_to_lines(jobs=os.cpu_count())
Yabin Cui6e25cdb2017-01-09 13:45:27 -080065
Yabin Cui58814c62018-08-07 12:12:31 -070066 def get_sources(self, dso_path, addr):
67 dso = self.addr2line.get_dso(dso_path)
68 if not dso:
Yabin Cui5b01fe62017-03-22 11:52:00 -070069 return []
Yabin Cui58814c62018-08-07 12:12:31 -070070 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 Cui6e25cdb2017-01-09 13:45:27 -080080
81
82class 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 Cuib44c3362021-06-01 13:14:05 -070091
Yabin Cui6e25cdb2017-01-09 13:45:27 -080092 def __init__(self, period=0, acc_period=0):
93 self.period = period
94 self.acc_period = acc_period
95
Yabin Cui6e25cdb2017-01-09 13:45:27 -080096 def __iadd__(self, other):
97 self.period += other.period
98 self.acc_period += other.acc_period
99 return self
100
101
102class DsoPeriod(object):
103 """Period for each shared library"""
Yabin Cuib44c3362021-06-01 13:14:05 -0700104
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800105 def __init__(self, dso_name):
106 self.dso_name = dso_name
107 self.period = Period()
108
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800109 def add_period(self, period):
110 self.period += period
111
112
113class FilePeriod(object):
114 """Period for each source file"""
Yabin Cuib44c3362021-06-01 13:14:05 -0700115
Yabin Cui58814c62018-08-07 12:12:31 -0700116 def __init__(self, file_id):
117 self.file = file_id
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800118 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 Cui6e25cdb2017-01-09 13:45:27 -0800124 def add_period(self, period):
125 self.period += period
126
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800127 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 Cui6e25cdb2017-01-09 13:45:27 -0800133 def add_function_period(self, function_name, function_start_line, period):
134 a = self.function_dict.get(function_name)
Yabin Cui5b01fe62017-03-22 11:52:00 -0700135 if not a:
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800136 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
142class SourceFileAnnotator(object):
143 """group code for annotating source files"""
Yabin Cuib44c3362021-06-01 13:14:05 -0700144
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800145 def __init__(self, config):
146 # check config variables
Yabin Cuie41e9202017-08-22 13:47:19 -0700147 config_names = ['perf_data_list', 'source_dirs', 'comm_filters',
Yabin Cui9758ecc2018-04-23 11:05:02 -0700148 'pid_filters', 'tid_filters', 'dso_filters', 'ndk_path']
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800149 for name in config_names:
Yabin Cui11fb6792017-06-29 10:52:39 -0700150 if name not in config:
Yabin Cuie41e9202017-08-22 13:47:19 -0700151 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 Cui6e25cdb2017-01-09 13:45:27 -0800158
159 # init member variables
160 self.config = config
Yabin Cuie41e9202017-08-22 13:47:19 -0700161 self.symfs_dir = symfs_dir
162 self.kallsyms = kallsyms
Yabin Cui5b01fe62017-03-22 11:52:00 -0700163 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 Cui6e25cdb2017-01-09 13:45:27 -0800173
Yabin Cuie41e9202017-08-22 13:47:19 -0700174 config['annotate_dest_dir'] = 'annotated_files'
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800175 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 Cui58814c62018-08-07 12:12:31 -0700180 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 Cui6e25cdb2017-01-09 13:45:27 -0800184
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800185 def annotate(self):
186 self._collect_addrs()
187 self._convert_addrs_to_lines()
188 self._generate_periods()
189 self._write_summary()
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800190 self._annotate_files()
191
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800192 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 Cui5b01fe62017-03-22 11:52:00 -0700198 lib.SetRecordFile(perf_data)
199 if self.symfs_dir:
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800200 lib.SetSymfs(self.symfs_dir)
Yabin Cui5b01fe62017-03-22 11:52:00 -0700201 if self.kallsyms:
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800202 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 Cuib44c3362021-06-01 13:14:05 -0700217 build_id = lib.GetBuildIdForPath(symbol.dso_name)
218 self.addr2line.add_addr(symbol.dso_name, build_id, symbol.symbol_addr,
Yabin Cui58814c62018-08-07 12:12:31 -0700219 symbol.vaddr_in_file)
Yabin Cuib44c3362021-06-01 13:14:05 -0700220 self.addr2line.add_addr(symbol.dso_name, build_id, symbol.symbol_addr,
Yabin Cui58814c62018-08-07 12:12:31 -0700221 symbol.symbol_addr)
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800222
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800223 def _filter_sample(self, sample):
224 """Return true if the sample can be used."""
Yabin Cui5b01fe62017-03-22 11:52:00 -0700225 if self.comm_filter:
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800226 if sample.thread_comm not in self.comm_filter:
227 return False
Yabin Cui5b01fe62017-03-22 11:52:00 -0700228 if self.pid_filter:
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800229 if sample.pid not in self.pid_filter:
230 return False
Yabin Cui5b01fe62017-03-22 11:52:00 -0700231 if self.tid_filter:
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800232 if sample.tid not in self.tid_filter:
233 return False
234 return True
235
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800236 def _filter_symbol(self, symbol):
Yabin Cui5b01fe62017-03-22 11:52:00 -0700237 if not self.dso_filter or symbol.dso_name in self.dso_filter:
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800238 return True
239 return False
240
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800241 def _convert_addrs_to_lines(self):
242 self.addr2line.convert_addrs_to_lines()
243
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800244 def _generate_periods(self):
245 """read perf.data, collect Period for all types:
246 binaries, source files, functions, lines.
247 """
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800248 for perf_data in self.config['perf_data_list']:
249 lib = ReportLib()
Yabin Cui5b01fe62017-03-22 11:52:00 -0700250 lib.SetRecordFile(perf_data)
251 if self.symfs_dir:
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800252 lib.SetSymfs(self.symfs_dir)
Yabin Cui5b01fe62017-03-22 11:52:00 -0700253 if self.kallsyms:
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800254 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 Cui58814c62018-08-07 12:12:31 -0700262 self._generate_periods_for_sample(lib, sample)
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800263
Yabin Cui58814c62018-08-07 12:12:31 -0700264 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 Cui6e25cdb2017-01-09 13:45:27 -0800307
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800308 def _add_dso_period(self, dso_name, period, used_dso_dict):
Yabin Cui11fb6792017-06-29 10:52:39 -0700309 if dso_name not in used_dso_dict:
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800310 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 Cui5b01fe62017-03-22 11:52:00 -0700316 def _add_file_period(self, source, period, used_file_dict):
Yabin Cui11fb6792017-06-29 10:52:39 -0700317 if source.file_key not in used_file_dict:
Yabin Cui5b01fe62017-03-22 11:52:00 -0700318 used_file_dict[source.file_key] = True
319 file_period = self.file_periods.get(source.file)
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800320 if file_period is None:
Yabin Cui5b01fe62017-03-22 11:52:00 -0700321 file_period = self.file_periods[source.file] = FilePeriod(source.file)
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800322 file_period.add_period(period)
323
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800324 def _add_line_period(self, source, period, used_line_dict):
Yabin Cui11fb6792017-06-29 10:52:39 -0700325 if source.line_key not in used_line_dict:
Yabin Cui5b01fe62017-03-22 11:52:00 -0700326 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 Cui6e25cdb2017-01-09 13:45:27 -0800329
Yabin Cui5b01fe62017-03-22 11:52:00 -0700330 def _add_function_period(self, source, period, used_function_dict):
Yabin Cui11fb6792017-06-29 10:52:39 -0700331 if source.function_key not in used_function_dict:
Yabin Cui5b01fe62017-03-22 11:52:00 -0700332 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 Cui6e25cdb2017-01-09 13:45:27 -0800335
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800336 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 Cui11fb6792017-06-29 10:52:39 -0700341 key=lambda x: x.period.acc_period, reverse=True)
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800342 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 Cui11fb6792017-06-29 10:52:39 -0700348 key=lambda x: x.period.acc_period, reverse=True)
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800349 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 Cui11fb6792017-06-29 10:52:39 -0700359 values = sorted(values, key=lambda x: x[2].acc_period, reverse=True)
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800360 for value in values:
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800361 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 Cui6e25cdb2017-01-09 13:45:27 -0800368 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 Cui6e25cdb2017-01-09 13:45:27 -0800372 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 Cui6e25cdb2017-01-09 13:45:27 -0800379 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 Cui58814c62018-08-07 12:12:31 -0700385 for key in self.file_periods:
386 from_path = key
387 if not os.path.isfile(from_path):
Yabin Cuifd4adae2021-10-25 13:55:01 -0700388 logging.warning("can't find source file for path %s" % from_path)
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800389 continue
Yabin Cui58814c62018-08-07 12:12:31 -0700390 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 Cui6e25cdb2017-01-09 13:45:27 -0800397 self._annotate_file(from_path, to_path, self.file_periods[key], is_java)
398
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800399 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 Cuifd4adae2021-10-25 13:55:01 -0700408 logging.info('annotate file %s' % from_path)
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800409 with open(from_path, 'r') as rf:
410 lines = rf.readlines()
411
Yabin Cui58814c62018-08-07 12:12:31 -0700412 annotates = {}
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800413 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 Cui58814c62018-08-07 12:12:31 -0700424 for key in annotates:
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800425 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 Cuib7f37d22017-05-08 11:48:55 -0700436 if not lines[line-1].strip():
437 annotate = ''
438 else:
439 annotate = empty_annotate
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800440 else:
441 annotate = '/* ' + annotate + (
442 ' ' * (max_annotate_cols - len(annotate))) + ' */'
443 wf.write(annotate)
444 wf.write(lines[line-1])
445
Yabin Cuib44c3362021-06-01 13:14:05 -0700446
Yabin Cuie41e9202017-08-22 13:47:19 -0700447def main():
Yabin Cuifd4adae2021-10-25 13:55:01 -0700448 parser = BaseArgumentParser(description="""
Yabin Cui58814c62018-08-07 12:12:31 -0700449 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 Cui9758ecc2018-04-23 11:05:02 -0700464 parser.add_argument('--ndk_path', type=extant_dir, help='Set the path of a ndk release.')
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800465
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800466 args = parser.parse_args()
Yabin Cuie41e9202017-08-22 13:47:19 -0700467 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 Cui9758ecc2018-04-23 11:05:02 -0700476 config['ndk_path'] = args.ndk_path
Yabin Cuie41e9202017-08-22 13:47:19 -0700477
Yabin Cui6e25cdb2017-01-09 13:45:27 -0800478 annotator = SourceFileAnnotator(config)
Yabin Cui5b01fe62017-03-22 11:52:00 -0700479 annotator.annotate()
Yabin Cuifd4adae2021-10-25 13:55:01 -0700480 logging.info('annotate finish successfully, please check result in annotated_files/.')
Yabin Cuie41e9202017-08-22 13:47:19 -0700481
Yabin Cuib44c3362021-06-01 13:14:05 -0700482
Yabin Cuie41e9202017-08-22 13:47:19 -0700483if __name__ == '__main__':
484 main()