xref: /aosp_15_r20/system/extras/simpleperf/scripts/annotate.py (revision 288bf5226967eb3dac5cce6c939ccc2a7f2b4fe5)
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