blob: a77f6a2183594ac8c04c533ecc753893f54cbbf9 [file] [log] [blame]
Yabin Cuid09d15b2016-12-12 18:18:07 -08001#!/usr/bin/env python
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
21
22import argparse
23import os
24import os.path
25import shutil
26import subprocess
27import sys
28
29from simpleperf_report_lib import *
30from utils import *
31
Yabin Cui129b5d22017-03-16 13:00:43 -070032class SourceLine(object):
33 def __init__(self, file, function, line):
34 self.file = file
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 Cuid09d15b2016-12-12 18:18:07 -080051# TODO: using addr2line can't convert from function_start_address to
52# source_file:line very well for java code. Because in .debug_line section,
53# there is some distance between function_start_address and the address
54# of the first instruction which can be mapped to source line.
55class Addr2Line(object):
56 """collect information of how to map [dso_name,vaddr] to [source_file:line].
57 """
Yabin Cui129b5d22017-03-16 13:00:43 -070058 def __init__(self, addr2line_path, symfs_dir=None):
Yabin Cuid09d15b2016-12-12 18:18:07 -080059 self.dso_dict = dict()
Yabin Cuid09d15b2016-12-12 18:18:07 -080060 self.addr2line_path = addr2line_path
Yabin Cui129b5d22017-03-16 13:00:43 -070061 self.symfs_dir = symfs_dir
Yabin Cuid09d15b2016-12-12 18:18:07 -080062
63
64 def add_addr(self, dso_name, addr):
65 dso = self.dso_dict.get(dso_name)
66 if dso is None:
67 self.dso_dict[dso_name] = dso = dict()
Yabin Cui98058a82017-05-08 14:23:19 -070068 if addr not in dso:
Yabin Cuid09d15b2016-12-12 18:18:07 -080069 dso[addr] = None
70
71
72 def convert_addrs_to_lines(self):
73 # store a list of source files
74 self.file_list = []
75 # map from file to id with file_list[id] == file
76 self.file_dict = {}
Yabin Cui129b5d22017-03-16 13:00:43 -070077 self.file_list.append('')
78 self.file_dict[''] = 0
Yabin Cuid09d15b2016-12-12 18:18:07 -080079
80 for dso_name in self.dso_dict.keys():
81 self._convert_addrs_to_lines(dso_name, self.dso_dict[dso_name])
82 self._combine_source_files()
83
84
85 def _convert_addrs_to_lines(self, dso_name, dso):
Yabin Cui129b5d22017-03-16 13:00:43 -070086 dso_path = self._find_dso_path(dso_name)
Yabin Cuid09d15b2016-12-12 18:18:07 -080087 if dso_path is None:
88 log_warning("can't find dso '%s'" % dso_name)
89 dso.clear()
90 return
Yabin Cui129b5d22017-03-16 13:00:43 -070091 addrs = sorted(dso.keys())
Yabin Cuid09d15b2016-12-12 18:18:07 -080092 addr_str = []
93 for addr in addrs:
94 addr_str.append('0x%x' % addr)
95 addr_str = '\n'.join(addr_str)
Yabin Cui129b5d22017-03-16 13:00:43 -070096 subproc = subprocess.Popen([self.addr2line_path, '-e', dso_path, '-aifC'],
Yabin Cuid09d15b2016-12-12 18:18:07 -080097 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Yabin Cui98058a82017-05-08 14:23:19 -070098 (stdoutdata, _) = subproc.communicate(str_to_bytes(addr_str))
99 stdoutdata = bytes_to_str(stdoutdata)
Yabin Cui129b5d22017-03-16 13:00:43 -0700100 stdoutdata = stdoutdata.strip().split('\n')
Yabin Cuid09d15b2016-12-12 18:18:07 -0800101 if len(stdoutdata) < len(addrs):
102 log_fatal("addr2line didn't output enough lines")
Yabin Cui129b5d22017-03-16 13:00:43 -0700103 addr_pos = 0
104 out_pos = 0
105 while addr_pos < len(addrs) and out_pos < len(stdoutdata):
106 addr_line = stdoutdata[out_pos]
107 out_pos += 1
108 assert addr_line[:2] == "0x"
109 assert out_pos < len(stdoutdata)
Yabin Cui129b5d22017-03-16 13:00:43 -0700110 source_lines = []
111 while out_pos < len(stdoutdata) and stdoutdata[out_pos][:2] != "0x":
112 function = stdoutdata[out_pos]
113 out_pos += 1
114 assert out_pos < len(stdoutdata)
Yabin Cuicbddd382017-05-03 14:46:55 -0700115 # Handle lines like "C:\Users\...\file:32".
116 items = stdoutdata[out_pos].rsplit(':', 1)
117 if len(items) != 2:
118 continue
119 (file, line) = items
Yabin Cui129b5d22017-03-16 13:00:43 -0700120 line = line.split()[0] # Remove comments after line number
121 out_pos += 1
122 if file.find('?') != -1:
123 file = 0
Yabin Cuid09d15b2016-12-12 18:18:07 -0800124 else:
Yabin Cui129b5d22017-03-16 13:00:43 -0700125 file = self._get_file_id(file)
126 if line.find('?') != -1:
127 line = 0
128 else:
129 line = int(line)
130 source_lines.append(SourceLine(file, function, line))
Yabin Cuicbddd382017-05-03 14:46:55 -0700131 dso[addrs[addr_pos]] = source_lines
132 addr_pos += 1
Yabin Cui129b5d22017-03-16 13:00:43 -0700133 assert addr_pos == len(addrs)
Yabin Cuid09d15b2016-12-12 18:18:07 -0800134
135
Yabin Cui129b5d22017-03-16 13:00:43 -0700136 def _get_file_id(self, file):
137 id = self.file_dict.get(file)
138 if id is None:
139 id = len(self.file_list)
140 self.file_list.append(file)
141 self.file_dict[file] = id
142 return id
143
Yabin Cuid09d15b2016-12-12 18:18:07 -0800144 def _combine_source_files(self):
145 """It is possible that addr2line gives us different names for the same
146 file, like:
147 /usr/local/.../src/main/jni/sudo-game-jni.cpp
148 sudo-game-jni.cpp
149 We'd better combine these two files. We can do it by combining
150 source files with no conflicts in path.
151 """
152 # Collect files having the same filename.
153 filename_dict = dict()
154 for file in self.file_list:
155 index = max(file.rfind('/'), file.rfind(os.sep))
156 filename = file[index+1:]
157 entry = filename_dict.get(filename)
158 if entry is None:
159 filename_dict[filename] = entry = []
160 entry.append(file)
161
162 # Combine files having the same filename and having no conflicts in path.
163 for filename in filename_dict.keys():
164 files = filename_dict[filename]
165 if len(files) == 1:
166 continue
167 for file in files:
168 to_file = file
169 # Test if we can merge files[i] with another file having longer
170 # path.
171 for f in files:
172 if len(f) > len(to_file) and f.find(file) != -1:
173 to_file = f
174 if to_file != file:
175 from_id = self.file_dict[file]
176 to_id = self.file_dict[to_file]
177 self.file_list[from_id] = self.file_list[to_id]
178
179
Yabin Cui129b5d22017-03-16 13:00:43 -0700180 def get_sources(self, dso_name, addr):
Yabin Cuid09d15b2016-12-12 18:18:07 -0800181 dso = self.dso_dict.get(dso_name)
182 if dso is None:
Yabin Cui129b5d22017-03-16 13:00:43 -0700183 return []
184 item = dso.get(addr, [])
185 source_lines = []
186 for source in item:
187 source_lines.append(SourceLine(self.file_list[source.file],
188 source.function, source.line))
189 return source_lines
190
191
192 def _find_dso_path(self, dso):
193 if dso[0] != '/' or dso == '//anon':
194 return None
195 if self.symfs_dir:
196 dso_path = os.path.join(self.symfs_dir, dso[1:])
197 if os.path.isfile(dso_path):
198 return dso_path
199 if os.path.isfile(dso):
200 return dso
201 return None
Yabin Cuid09d15b2016-12-12 18:18:07 -0800202
203
204class Period(object):
205 """event count information. It can be used to represent event count
206 of a line, a function, a source file, or a binary. It contains two
207 parts: period and acc_period.
208 When used for a line, period is the event count occurred when running
209 that line, acc_period is the accumulated event count occurred when
210 running that line and functions called by that line. Same thing applies
211 when it is used for a function, a source file, or a binary.
212 """
213 def __init__(self, period=0, acc_period=0):
214 self.period = period
215 self.acc_period = acc_period
216
217
218 def __iadd__(self, other):
219 self.period += other.period
220 self.acc_period += other.acc_period
221 return self
222
223
224class DsoPeriod(object):
225 """Period for each shared library"""
226 def __init__(self, dso_name):
227 self.dso_name = dso_name
228 self.period = Period()
229
230
231 def add_period(self, period):
232 self.period += period
233
234
235class FilePeriod(object):
236 """Period for each source file"""
237 def __init__(self, file):
238 self.file = file
239 self.period = Period()
240 # Period for each line in the file.
241 self.line_dict = {}
242 # Period for each function in the source file.
243 self.function_dict = {}
244
245
246 def add_period(self, period):
247 self.period += period
248
249
250 def add_line_period(self, line, period):
251 a = self.line_dict.get(line)
252 if a is None:
253 self.line_dict[line] = a = Period()
254 a += period
255
256
257 def add_function_period(self, function_name, function_start_line, period):
258 a = self.function_dict.get(function_name)
Yabin Cui129b5d22017-03-16 13:00:43 -0700259 if not a:
Yabin Cuid09d15b2016-12-12 18:18:07 -0800260 if function_start_line is None:
261 function_start_line = -1
262 self.function_dict[function_name] = a = [function_start_line, Period()]
263 a[1] += period
264
265
266class SourceFileAnnotator(object):
267 """group code for annotating source files"""
268 def __init__(self, config):
269 # check config variables
270 config_names = ['perf_data_list', 'symfs_dir', 'source_dirs',
271 'annotate_dest_dir', 'comm_filters', 'pid_filters',
272 'tid_filters', 'dso_filters', 'addr2line_path']
273 for name in config_names:
Yabin Cui98058a82017-05-08 14:23:19 -0700274 if name not in config:
Yabin Cuid09d15b2016-12-12 18:18:07 -0800275 log_fatal('config [%s] is missing' % name)
276 symfs_dir = config['symfs_dir']
277 if symfs_dir and not os.path.isdir(symfs_dir):
278 log_fatal('[symfs_dir] "%s" is not a dir' % symfs_dir)
279 kallsyms = config['kallsyms']
280 if kallsyms and not os.path.isfile(kallsyms):
281 log_fatal('[kallsyms] "%s" is not a file' % kallsyms)
282 source_dirs = config['source_dirs']
283 for dir in source_dirs:
284 if not os.path.isdir(dir):
285 log_fatal('[source_dirs] "%s" is not a dir' % dir)
286
287 # init member variables
288 self.config = config
Yabin Cui129b5d22017-03-16 13:00:43 -0700289 self.symfs_dir = config.get('symfs_dir')
290 self.kallsyms = config.get('kallsyms')
291 self.comm_filter = set(config['comm_filters']) if config.get('comm_filters') else None
292 if config.get('pid_filters'):
293 self.pid_filter = {int(x) for x in config['pid_filters']}
294 else:
295 self.pid_filter = None
296 if config.get('tid_filters'):
297 self.tid_filter = {int(x) for x in config['tid_filters']}
298 else:
299 self.tid_filter = None
300 self.dso_filter = set(config['dso_filters']) if config.get('dso_filters') else None
Yabin Cuid09d15b2016-12-12 18:18:07 -0800301
302 output_dir = config['annotate_dest_dir']
303 if os.path.isdir(output_dir):
304 shutil.rmtree(output_dir)
305 os.makedirs(output_dir)
306
Yabin Cui129b5d22017-03-16 13:00:43 -0700307 self.addr2line = Addr2Line(self.config['addr2line_path'], symfs_dir)
Yabin Cuid09d15b2016-12-12 18:18:07 -0800308
309
310 def annotate(self):
311 self._collect_addrs()
312 self._convert_addrs_to_lines()
313 self._generate_periods()
314 self._write_summary()
315 self._collect_source_files()
316 self._annotate_files()
317
318
319 def _collect_addrs(self):
320 """Read perf.data, collect all addresses we need to convert to
321 source file:line.
322 """
323 for perf_data in self.config['perf_data_list']:
324 lib = ReportLib()
Yabin Cui129b5d22017-03-16 13:00:43 -0700325 lib.SetRecordFile(perf_data)
326 if self.symfs_dir:
Yabin Cuid09d15b2016-12-12 18:18:07 -0800327 lib.SetSymfs(self.symfs_dir)
Yabin Cui129b5d22017-03-16 13:00:43 -0700328 if self.kallsyms:
Yabin Cuid09d15b2016-12-12 18:18:07 -0800329 lib.SetKallsymsFile(self.kallsyms)
330 while True:
331 sample = lib.GetNextSample()
332 if sample is None:
333 lib.Close()
334 break
335 if not self._filter_sample(sample):
336 continue
337 symbols = []
338 symbols.append(lib.GetSymbolOfCurrentSample())
339 callchain = lib.GetCallChainOfCurrentSample()
340 for i in range(callchain.nr):
341 symbols.append(callchain.entries[i].symbol)
342 for symbol in symbols:
343 if self._filter_symbol(symbol):
344 self.addr2line.add_addr(symbol.dso_name, symbol.vaddr_in_file)
345 self.addr2line.add_addr(symbol.dso_name, symbol.symbol_addr)
346
347
348 def _filter_sample(self, sample):
349 """Return true if the sample can be used."""
Yabin Cui129b5d22017-03-16 13:00:43 -0700350 if self.comm_filter:
Yabin Cuid09d15b2016-12-12 18:18:07 -0800351 if sample.thread_comm not in self.comm_filter:
352 return False
Yabin Cui129b5d22017-03-16 13:00:43 -0700353 if self.pid_filter:
Yabin Cuid09d15b2016-12-12 18:18:07 -0800354 if sample.pid not in self.pid_filter:
355 return False
Yabin Cui129b5d22017-03-16 13:00:43 -0700356 if self.tid_filter:
Yabin Cuid09d15b2016-12-12 18:18:07 -0800357 if sample.tid not in self.tid_filter:
358 return False
359 return True
360
361
362 def _filter_symbol(self, symbol):
Yabin Cui129b5d22017-03-16 13:00:43 -0700363 if not self.dso_filter or symbol.dso_name in self.dso_filter:
Yabin Cuid09d15b2016-12-12 18:18:07 -0800364 return True
365 return False
366
367
368 def _convert_addrs_to_lines(self):
369 self.addr2line.convert_addrs_to_lines()
370
371
Yabin Cuid09d15b2016-12-12 18:18:07 -0800372 def _generate_periods(self):
373 """read perf.data, collect Period for all types:
374 binaries, source files, functions, lines.
375 """
376 self.period = 0
377 self.dso_periods = dict()
378 self.file_periods = dict()
379 for perf_data in self.config['perf_data_list']:
380 lib = ReportLib()
Yabin Cui129b5d22017-03-16 13:00:43 -0700381 lib.SetRecordFile(perf_data)
382 if self.symfs_dir:
Yabin Cuid09d15b2016-12-12 18:18:07 -0800383 lib.SetSymfs(self.symfs_dir)
Yabin Cui129b5d22017-03-16 13:00:43 -0700384 if self.kallsyms:
Yabin Cuid09d15b2016-12-12 18:18:07 -0800385 lib.SetKallsymsFile(self.kallsyms)
386 while True:
387 sample = lib.GetNextSample()
388 if sample is None:
389 lib.Close()
390 break
391 if not self._filter_sample(sample):
392 continue
393 symbols = []
394 symbols.append(lib.GetSymbolOfCurrentSample())
395 callchain = lib.GetCallChainOfCurrentSample()
396 for i in range(callchain.nr):
397 symbols.append(callchain.entries[i].symbol)
398 # Each sample has a callchain, but its period is only used once
399 # to add period for each function/source_line/source_file/binary.
400 # For example, if more than one entry in the callchain hits a
401 # function, the event count of that function is only increased once.
402 # Otherwise, we may get periods > 100%.
403 is_sample_used = False
404 used_dso_dict = dict()
405 used_file_dict = dict()
406 used_function_dict = dict()
407 used_line_dict = dict()
408 period = Period(sample.period, sample.period)
409 for i in range(len(symbols)):
410 symbol = symbols[i]
411 if i == 1:
412 period = Period(0, sample.period)
413 if not self._filter_symbol(symbol):
414 continue
415 is_sample_used = True
416 # Add period to dso.
417 self._add_dso_period(symbol.dso_name, period, used_dso_dict)
418 # Add period to source file.
Yabin Cui129b5d22017-03-16 13:00:43 -0700419 sources = self.addr2line.get_sources(symbol.dso_name, symbol.vaddr_in_file)
420 for source in sources:
421 if source.file:
422 self._add_file_period(source, period, used_file_dict)
423 # Add period to line.
424 if source.line:
425 self._add_line_period(source, period, used_line_dict)
Yabin Cuid09d15b2016-12-12 18:18:07 -0800426 # Add period to function.
Yabin Cui129b5d22017-03-16 13:00:43 -0700427 sources = self.addr2line.get_sources(symbol.dso_name, symbol.symbol_addr)
428 for source in sources:
429 if source.file:
430 self._add_file_period(source, period, used_file_dict)
431 if source.function:
432 self._add_function_period(source, period, used_function_dict)
Yabin Cuid09d15b2016-12-12 18:18:07 -0800433
434 if is_sample_used:
435 self.period += sample.period
436
437
438 def _add_dso_period(self, dso_name, period, used_dso_dict):
Yabin Cui98058a82017-05-08 14:23:19 -0700439 if dso_name not in used_dso_dict:
Yabin Cuid09d15b2016-12-12 18:18:07 -0800440 used_dso_dict[dso_name] = True
441 dso_period = self.dso_periods.get(dso_name)
442 if dso_period is None:
443 dso_period = self.dso_periods[dso_name] = DsoPeriod(dso_name)
444 dso_period.add_period(period)
445
446
Yabin Cui129b5d22017-03-16 13:00:43 -0700447 def _add_file_period(self, source, period, used_file_dict):
Yabin Cui98058a82017-05-08 14:23:19 -0700448 if source.file_key not in used_file_dict:
Yabin Cui129b5d22017-03-16 13:00:43 -0700449 used_file_dict[source.file_key] = True
450 file_period = self.file_periods.get(source.file)
Yabin Cuid09d15b2016-12-12 18:18:07 -0800451 if file_period is None:
Yabin Cui129b5d22017-03-16 13:00:43 -0700452 file_period = self.file_periods[source.file] = FilePeriod(source.file)
Yabin Cuid09d15b2016-12-12 18:18:07 -0800453 file_period.add_period(period)
454
455
456 def _add_line_period(self, source, period, used_line_dict):
Yabin Cui98058a82017-05-08 14:23:19 -0700457 if source.line_key not in used_line_dict:
Yabin Cui129b5d22017-03-16 13:00:43 -0700458 used_line_dict[source.line_key] = True
459 file_period = self.file_periods[source.file]
460 file_period.add_line_period(source.line, period)
Yabin Cuid09d15b2016-12-12 18:18:07 -0800461
462
Yabin Cui129b5d22017-03-16 13:00:43 -0700463 def _add_function_period(self, source, period, used_function_dict):
Yabin Cui98058a82017-05-08 14:23:19 -0700464 if source.function_key not in used_function_dict:
Yabin Cui129b5d22017-03-16 13:00:43 -0700465 used_function_dict[source.function_key] = True
466 file_period = self.file_periods[source.file]
467 file_period.add_function_period(source.function, source.line, period)
Yabin Cuid09d15b2016-12-12 18:18:07 -0800468
469
470 def _write_summary(self):
471 summary = os.path.join(self.config['annotate_dest_dir'], 'summary')
472 with open(summary, 'w') as f:
473 f.write('total period: %d\n\n' % self.period)
474 dso_periods = sorted(self.dso_periods.values(),
Yabin Cui98058a82017-05-08 14:23:19 -0700475 key=lambda x: x.period.acc_period, reverse=True)
Yabin Cuid09d15b2016-12-12 18:18:07 -0800476 for dso_period in dso_periods:
477 f.write('dso %s: %s\n' % (dso_period.dso_name,
478 self._get_percentage_str(dso_period.period)))
479 f.write('\n')
480
481 file_periods = sorted(self.file_periods.values(),
Yabin Cui98058a82017-05-08 14:23:19 -0700482 key=lambda x: x.period.acc_period, reverse=True)
Yabin Cuid09d15b2016-12-12 18:18:07 -0800483 for file_period in file_periods:
484 f.write('file %s: %s\n' % (file_period.file,
485 self._get_percentage_str(file_period.period)))
486 for file_period in file_periods:
487 f.write('\n\n%s: %s\n' % (file_period.file,
488 self._get_percentage_str(file_period.period)))
489 values = []
490 for func_name in file_period.function_dict.keys():
491 func_start_line, period = file_period.function_dict[func_name]
492 values.append((func_name, func_start_line, period))
Yabin Cui98058a82017-05-08 14:23:19 -0700493 values = sorted(values, key=lambda x: x[2].acc_period, reverse=True)
Yabin Cuid09d15b2016-12-12 18:18:07 -0800494 for value in values:
Yabin Cuid09d15b2016-12-12 18:18:07 -0800495 f.write('\tfunction (%s): line %d, %s\n' % (
496 value[0], value[1], self._get_percentage_str(value[2])))
497 f.write('\n')
498 for line in sorted(file_period.line_dict.keys()):
499 f.write('\tline %d: %s\n' % (
500 line, self._get_percentage_str(file_period.line_dict[line])))
501
502
503 def _get_percentage_str(self, period, short=False):
504 s = 'acc_p: %f%%, p: %f%%' if short else 'accumulated_period: %f%%, period: %f%%'
505 return s % self._get_percentage(period)
506
507
508 def _get_percentage(self, period):
509 if self.period == 0:
510 return (0, 0)
511 acc_p = 100.0 * period.acc_period / self.period
512 p = 100.0 * period.period / self.period
513 return (acc_p, p)
514
515
516 def _collect_source_files(self):
517 self.source_file_dict = dict()
518 source_file_suffix = ['h', 'c', 'cpp', 'cc', 'java']
519 for source_dir in self.config['source_dirs']:
520 for root, _, files in os.walk(source_dir):
521 for file in files:
522 if file[file.rfind('.')+1:] in source_file_suffix:
523 entry = self.source_file_dict.get(file)
524 if entry is None:
525 entry = self.source_file_dict[file] = []
526 entry.append(os.path.join(root, file))
527
528
529 def _find_source_file(self, file):
530 filename = file[file.rfind(os.sep)+1:]
531 source_files = self.source_file_dict.get(filename)
532 if source_files is None:
533 return None
534 match_count = 0
535 result = None
536 for path in source_files:
537 if path.find(file) != -1:
538 match_count += 1
539 result = path
540 if match_count > 1:
541 log_warning('multiple source for %s, select %s' % (file, result))
542 return result
543
544
545 def _annotate_files(self):
546 """Annotate Source files: add acc_period/period for each source file.
547 1. Annotate java source files, which have $JAVA_SRC_ROOT prefix.
548 2. Annotate c++ source files.
549 """
550 dest_dir = self.config['annotate_dest_dir']
551 for key in self.file_periods.keys():
Yabin Cui5fac4382017-01-06 12:47:31 -0800552 is_java = False
Yabin Cuid09d15b2016-12-12 18:18:07 -0800553 if key.startswith('$JAVA_SRC_ROOT/'):
554 path = key[len('$JAVA_SRC_ROOT/'):]
555 items = path.split('/')
556 path = os.sep.join(items)
557 from_path = self._find_source_file(path)
558 to_path = os.path.join(dest_dir, 'java', path)
559 is_java = True
560 elif key.startswith('/') and os.path.isfile(key):
561 path = key
562 from_path = path
563 to_path = os.path.join(dest_dir, path[1:])
Yabin Cuicbddd382017-05-03 14:46:55 -0700564 elif is_windows() and key.find(':\\') != -1 and os.path.isfile(key):
565 from_path = key
566 to_path = os.path.join(dest_dir, key.replace(':\\', '\\'))
Yabin Cuid09d15b2016-12-12 18:18:07 -0800567 else:
568 path = key[1:] if key.startswith('/') else key
569 # Change path on device to path on host
570 path = os.sep.join(path.split('/'))
571 from_path = self._find_source_file(path)
572 to_path = os.path.join(dest_dir, path)
573 if from_path is None:
574 log_warning("can't find source file for path %s" % key)
575 continue
576 self._annotate_file(from_path, to_path, self.file_periods[key], is_java)
577
578
579 def _annotate_file(self, from_path, to_path, file_period, is_java):
580 """Annotate a source file.
581
582 Annotate a source file in three steps:
583 1. In the first line, show periods of this file.
584 2. For each function, show periods of this function.
585 3. For each line not hitting the same line as functions, show
586 line periods.
587 """
588 log_info('annotate file %s' % from_path)
589 with open(from_path, 'r') as rf:
590 lines = rf.readlines()
591
592 annotates = dict()
593 for line in file_period.line_dict.keys():
594 annotates[line] = self._get_percentage_str(file_period.line_dict[line], True)
595 for func_name in file_period.function_dict.keys():
596 func_start_line, period = file_period.function_dict[func_name]
597 if func_start_line == -1:
598 continue
599 line = func_start_line - 1 if is_java else func_start_line
600 annotates[line] = '[func] ' + self._get_percentage_str(period, True)
601 annotates[1] = '[file] ' + self._get_percentage_str(file_period.period, True)
602
603 max_annotate_cols = 0
604 for key in annotates.keys():
605 max_annotate_cols = max(max_annotate_cols, len(annotates[key]))
606
607 empty_annotate = ' ' * (max_annotate_cols + 6)
608
609 dirname = os.path.dirname(to_path)
610 if not os.path.isdir(dirname):
611 os.makedirs(dirname)
612 with open(to_path, 'w') as wf:
613 for line in range(1, len(lines) + 1):
614 annotate = annotates.get(line)
615 if annotate is None:
Yabin Cui06956f32017-04-28 10:44:16 -0700616 if not lines[line-1].strip():
617 annotate = ''
618 else:
619 annotate = empty_annotate
Yabin Cuid09d15b2016-12-12 18:18:07 -0800620 else:
621 annotate = '/* ' + annotate + (
622 ' ' * (max_annotate_cols - len(annotate))) + ' */'
623 wf.write(annotate)
624 wf.write(lines[line-1])
625
626
627if __name__ == '__main__':
628 parser = argparse.ArgumentParser(
629 description='Annotate based on perf.data. See configurations in annotate.config.')
630 parser.add_argument('--config', default='annotate.config',
631 help='Set configuration file. Default is annotate.config.')
632 args = parser.parse_args()
633 config = load_config(args.config)
634 annotator = SourceFileAnnotator(config)
Yabin Cui129b5d22017-03-16 13:00:43 -0700635 annotator.annotate()
Yabin Cuicbddd382017-05-03 14:46:55 -0700636 log_info('annotate finish successfully, please check result in annotated_files/.')