xref: /aosp_15_r20/system/extras/simpleperf/scripts/report_html.py (revision 288bf5226967eb3dac5cce6c939ccc2a7f2b4fe5)
1*288bf522SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*288bf522SAndroid Build Coastguard Worker#
3*288bf522SAndroid Build Coastguard Worker# Copyright (C) 2017 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 Workerfrom __future__ import annotations
19*288bf522SAndroid Build Coastguard Workerimport argparse
20*288bf522SAndroid Build Coastguard Workerimport collections
21*288bf522SAndroid Build Coastguard Workerfrom concurrent.futures import Future, ThreadPoolExecutor
22*288bf522SAndroid Build Coastguard Workerfrom dataclasses import dataclass
23*288bf522SAndroid Build Coastguard Workerimport datetime
24*288bf522SAndroid Build Coastguard Workerimport json
25*288bf522SAndroid Build Coastguard Workerimport logging
26*288bf522SAndroid Build Coastguard Workerimport os
27*288bf522SAndroid Build Coastguard Workerfrom pathlib import Path
28*288bf522SAndroid Build Coastguard Workerimport sys
29*288bf522SAndroid Build Coastguard Workerfrom typing import Any, Callable, Dict, Iterator, List, Optional, Set, Tuple, Union
30*288bf522SAndroid Build Coastguard Worker
31*288bf522SAndroid Build Coastguard Workerfrom simpleperf_report_lib import GetReportLib, SymbolStruct
32*288bf522SAndroid Build Coastguard Workerfrom simpleperf_utils import (
33*288bf522SAndroid Build Coastguard Worker    Addr2Nearestline, AddrRange, BaseArgumentParser, BinaryFinder, Disassembly, get_script_dir,
34*288bf522SAndroid Build Coastguard Worker    log_exit, Objdump, open_report_in_browser, ReadElf, ReportLibOptions, SourceFileSearcher)
35*288bf522SAndroid Build Coastguard Worker
36*288bf522SAndroid Build Coastguard WorkerMAX_CALLSTACK_LENGTH = 750
37*288bf522SAndroid Build Coastguard Worker
38*288bf522SAndroid Build Coastguard Worker
39*288bf522SAndroid Build Coastguard Workerclass HtmlWriter(object):
40*288bf522SAndroid Build Coastguard Worker
41*288bf522SAndroid Build Coastguard Worker    def __init__(self, output_path: Union[Path, str]):
42*288bf522SAndroid Build Coastguard Worker        self.fh = open(output_path, 'w')
43*288bf522SAndroid Build Coastguard Worker        self.tag_stack = []
44*288bf522SAndroid Build Coastguard Worker
45*288bf522SAndroid Build Coastguard Worker    def close(self):
46*288bf522SAndroid Build Coastguard Worker        self.fh.close()
47*288bf522SAndroid Build Coastguard Worker
48*288bf522SAndroid Build Coastguard Worker    def open_tag(self, tag: str, **attrs: Dict[str, str]) -> HtmlWriter:
49*288bf522SAndroid Build Coastguard Worker        attr_str = ''
50*288bf522SAndroid Build Coastguard Worker        for key in attrs:
51*288bf522SAndroid Build Coastguard Worker            attr_str += ' %s="%s"' % (key, attrs[key])
52*288bf522SAndroid Build Coastguard Worker        self.fh.write('<%s%s>' % (tag, attr_str))
53*288bf522SAndroid Build Coastguard Worker        self.tag_stack.append(tag)
54*288bf522SAndroid Build Coastguard Worker        return self
55*288bf522SAndroid Build Coastguard Worker
56*288bf522SAndroid Build Coastguard Worker    def close_tag(self, tag: Optional[str] = None):
57*288bf522SAndroid Build Coastguard Worker        if tag:
58*288bf522SAndroid Build Coastguard Worker            assert tag == self.tag_stack[-1]
59*288bf522SAndroid Build Coastguard Worker        self.fh.write('</%s>\n' % self.tag_stack.pop())
60*288bf522SAndroid Build Coastguard Worker
61*288bf522SAndroid Build Coastguard Worker    def add(self, text: str) -> HtmlWriter:
62*288bf522SAndroid Build Coastguard Worker        self.fh.write(text)
63*288bf522SAndroid Build Coastguard Worker        return self
64*288bf522SAndroid Build Coastguard Worker
65*288bf522SAndroid Build Coastguard Worker    def add_file(self, file_path: Union[Path, str]) -> HtmlWriter:
66*288bf522SAndroid Build Coastguard Worker        file_path = os.path.join(get_script_dir(), file_path)
67*288bf522SAndroid Build Coastguard Worker        with open(file_path, 'r') as f:
68*288bf522SAndroid Build Coastguard Worker            self.add(f.read())
69*288bf522SAndroid Build Coastguard Worker        return self
70*288bf522SAndroid Build Coastguard Worker
71*288bf522SAndroid Build Coastguard Worker
72*288bf522SAndroid Build Coastguard Workerdef modify_text_for_html(text: str) -> str:
73*288bf522SAndroid Build Coastguard Worker    return text.replace('>', '&gt;').replace('<', '&lt;')
74*288bf522SAndroid Build Coastguard Worker
75*288bf522SAndroid Build Coastguard Worker
76*288bf522SAndroid Build Coastguard Workerdef hex_address_for_json(addr: int) -> str:
77*288bf522SAndroid Build Coastguard Worker    """ To handle big addrs (nears uint64_max) in Javascript, store addrs as hex strings in Json.
78*288bf522SAndroid Build Coastguard Worker    """
79*288bf522SAndroid Build Coastguard Worker    return '0x%x' % addr
80*288bf522SAndroid Build Coastguard Worker
81*288bf522SAndroid Build Coastguard Worker
82*288bf522SAndroid Build Coastguard Workerclass EventScope(object):
83*288bf522SAndroid Build Coastguard Worker
84*288bf522SAndroid Build Coastguard Worker    def __init__(self, name: str):
85*288bf522SAndroid Build Coastguard Worker        self.name = name
86*288bf522SAndroid Build Coastguard Worker        self.processes: Dict[int, ProcessScope] = {}  # map from pid to ProcessScope
87*288bf522SAndroid Build Coastguard Worker        self.sample_count = 0
88*288bf522SAndroid Build Coastguard Worker        self.event_count = 0
89*288bf522SAndroid Build Coastguard Worker
90*288bf522SAndroid Build Coastguard Worker    def get_process(self, pid: int) -> ProcessScope:
91*288bf522SAndroid Build Coastguard Worker        process = self.processes.get(pid)
92*288bf522SAndroid Build Coastguard Worker        if not process:
93*288bf522SAndroid Build Coastguard Worker            process = self.processes[pid] = ProcessScope(pid)
94*288bf522SAndroid Build Coastguard Worker        return process
95*288bf522SAndroid Build Coastguard Worker
96*288bf522SAndroid Build Coastguard Worker    def get_sample_info(self, gen_addr_hit_map: bool) -> Dict[str, Any]:
97*288bf522SAndroid Build Coastguard Worker        result = {}
98*288bf522SAndroid Build Coastguard Worker        result['eventName'] = self.name
99*288bf522SAndroid Build Coastguard Worker        result['eventCount'] = self.event_count
100*288bf522SAndroid Build Coastguard Worker        processes = sorted(self.processes.values(), key=lambda a: a.event_count, reverse=True)
101*288bf522SAndroid Build Coastguard Worker        result['processes'] = [process.get_sample_info(gen_addr_hit_map)
102*288bf522SAndroid Build Coastguard Worker                               for process in processes]
103*288bf522SAndroid Build Coastguard Worker        return result
104*288bf522SAndroid Build Coastguard Worker
105*288bf522SAndroid Build Coastguard Worker    @property
106*288bf522SAndroid Build Coastguard Worker    def threads(self) -> Iterator[ThreadScope]:
107*288bf522SAndroid Build Coastguard Worker        for process in self.processes.values():
108*288bf522SAndroid Build Coastguard Worker            for thread in process.threads.values():
109*288bf522SAndroid Build Coastguard Worker                yield thread
110*288bf522SAndroid Build Coastguard Worker
111*288bf522SAndroid Build Coastguard Worker    @property
112*288bf522SAndroid Build Coastguard Worker    def libraries(self) -> Iterator[LibScope]:
113*288bf522SAndroid Build Coastguard Worker        for process in self.processes.values():
114*288bf522SAndroid Build Coastguard Worker            for thread in process.threads.values():
115*288bf522SAndroid Build Coastguard Worker                for lib in thread.libs.values():
116*288bf522SAndroid Build Coastguard Worker                    yield lib
117*288bf522SAndroid Build Coastguard Worker
118*288bf522SAndroid Build Coastguard Worker
119*288bf522SAndroid Build Coastguard Workerclass ProcessScope(object):
120*288bf522SAndroid Build Coastguard Worker
121*288bf522SAndroid Build Coastguard Worker    def __init__(self, pid: int):
122*288bf522SAndroid Build Coastguard Worker        self.pid = pid
123*288bf522SAndroid Build Coastguard Worker        self.name = ''
124*288bf522SAndroid Build Coastguard Worker        self.event_count = 0
125*288bf522SAndroid Build Coastguard Worker        self.threads: Dict[int, ThreadScope] = {}  # map from tid to ThreadScope
126*288bf522SAndroid Build Coastguard Worker
127*288bf522SAndroid Build Coastguard Worker    def get_thread(self, tid: int, thread_name: str) -> ThreadScope:
128*288bf522SAndroid Build Coastguard Worker        thread = self.threads.get(tid)
129*288bf522SAndroid Build Coastguard Worker        if not thread:
130*288bf522SAndroid Build Coastguard Worker            thread = self.threads[tid] = ThreadScope(tid)
131*288bf522SAndroid Build Coastguard Worker        thread.name = thread_name
132*288bf522SAndroid Build Coastguard Worker        if self.pid == tid:
133*288bf522SAndroid Build Coastguard Worker            self.name = thread_name
134*288bf522SAndroid Build Coastguard Worker        return thread
135*288bf522SAndroid Build Coastguard Worker
136*288bf522SAndroid Build Coastguard Worker    def get_sample_info(self, gen_addr_hit_map: bool) -> Dict[str, Any]:
137*288bf522SAndroid Build Coastguard Worker        result = {}
138*288bf522SAndroid Build Coastguard Worker        result['pid'] = self.pid
139*288bf522SAndroid Build Coastguard Worker        result['eventCount'] = self.event_count
140*288bf522SAndroid Build Coastguard Worker        # Sorting threads by sample count is better for profiles recorded with --trace-offcpu.
141*288bf522SAndroid Build Coastguard Worker        threads = sorted(self.threads.values(), key=lambda a: a.sample_count, reverse=True)
142*288bf522SAndroid Build Coastguard Worker        result['threads'] = [thread.get_sample_info(gen_addr_hit_map)
143*288bf522SAndroid Build Coastguard Worker                             for thread in threads]
144*288bf522SAndroid Build Coastguard Worker        return result
145*288bf522SAndroid Build Coastguard Worker
146*288bf522SAndroid Build Coastguard Worker    def merge_by_thread_name(self, process: ProcessScope):
147*288bf522SAndroid Build Coastguard Worker        self.event_count += process.event_count
148*288bf522SAndroid Build Coastguard Worker        thread_list: List[ThreadScope] = list(
149*288bf522SAndroid Build Coastguard Worker            self.threads.values()) + list(process.threads.values())
150*288bf522SAndroid Build Coastguard Worker        new_threads: Dict[str, ThreadScope] = {}  # map from thread name to ThreadScope
151*288bf522SAndroid Build Coastguard Worker        for thread in thread_list:
152*288bf522SAndroid Build Coastguard Worker            cur_thread = new_threads.get(thread.name)
153*288bf522SAndroid Build Coastguard Worker            if cur_thread is None:
154*288bf522SAndroid Build Coastguard Worker                new_threads[thread.name] = thread
155*288bf522SAndroid Build Coastguard Worker            else:
156*288bf522SAndroid Build Coastguard Worker                cur_thread.merge(thread)
157*288bf522SAndroid Build Coastguard Worker        self.threads = {}
158*288bf522SAndroid Build Coastguard Worker        for thread in new_threads.values():
159*288bf522SAndroid Build Coastguard Worker            self.threads[thread.tid] = thread
160*288bf522SAndroid Build Coastguard Worker
161*288bf522SAndroid Build Coastguard Worker
162*288bf522SAndroid Build Coastguard Workerclass ThreadScope(object):
163*288bf522SAndroid Build Coastguard Worker
164*288bf522SAndroid Build Coastguard Worker    def __init__(self, tid: int):
165*288bf522SAndroid Build Coastguard Worker        self.tid = tid
166*288bf522SAndroid Build Coastguard Worker        self.name = ''
167*288bf522SAndroid Build Coastguard Worker        self.event_count = 0
168*288bf522SAndroid Build Coastguard Worker        self.sample_count = 0
169*288bf522SAndroid Build Coastguard Worker        self.libs: Dict[int, LibScope] = {}  # map from lib_id to LibScope
170*288bf522SAndroid Build Coastguard Worker        self.call_graph = CallNode(-1)
171*288bf522SAndroid Build Coastguard Worker        self.reverse_call_graph = CallNode(-1)
172*288bf522SAndroid Build Coastguard Worker
173*288bf522SAndroid Build Coastguard Worker    def add_callstack(
174*288bf522SAndroid Build Coastguard Worker            self, event_count: int, callstack: List[Tuple[int, int, int]],
175*288bf522SAndroid Build Coastguard Worker            build_addr_hit_map: bool):
176*288bf522SAndroid Build Coastguard Worker        """ callstack is a list of tuple (lib_id, func_id, addr).
177*288bf522SAndroid Build Coastguard Worker            For each i > 0, callstack[i] calls callstack[i-1]."""
178*288bf522SAndroid Build Coastguard Worker        hit_func_ids: Set[int] = set()
179*288bf522SAndroid Build Coastguard Worker        for i, (lib_id, func_id, addr) in enumerate(callstack):
180*288bf522SAndroid Build Coastguard Worker            # When a callstack contains recursive function, only add for each function once.
181*288bf522SAndroid Build Coastguard Worker            if func_id in hit_func_ids:
182*288bf522SAndroid Build Coastguard Worker                continue
183*288bf522SAndroid Build Coastguard Worker            hit_func_ids.add(func_id)
184*288bf522SAndroid Build Coastguard Worker
185*288bf522SAndroid Build Coastguard Worker            lib = self.libs.get(lib_id)
186*288bf522SAndroid Build Coastguard Worker            if not lib:
187*288bf522SAndroid Build Coastguard Worker                lib = self.libs[lib_id] = LibScope(lib_id)
188*288bf522SAndroid Build Coastguard Worker            function = lib.get_function(func_id)
189*288bf522SAndroid Build Coastguard Worker            function.subtree_event_count += event_count
190*288bf522SAndroid Build Coastguard Worker            if i == 0:
191*288bf522SAndroid Build Coastguard Worker                lib.event_count += event_count
192*288bf522SAndroid Build Coastguard Worker                function.event_count += event_count
193*288bf522SAndroid Build Coastguard Worker                function.sample_count += 1
194*288bf522SAndroid Build Coastguard Worker            if build_addr_hit_map:
195*288bf522SAndroid Build Coastguard Worker                function.build_addr_hit_map(addr, event_count if i == 0 else 0, event_count)
196*288bf522SAndroid Build Coastguard Worker
197*288bf522SAndroid Build Coastguard Worker        # build call graph and reverse call graph
198*288bf522SAndroid Build Coastguard Worker        node = self.call_graph
199*288bf522SAndroid Build Coastguard Worker        for item in reversed(callstack):
200*288bf522SAndroid Build Coastguard Worker            node = node.get_child(item[1])
201*288bf522SAndroid Build Coastguard Worker        node.event_count += event_count
202*288bf522SAndroid Build Coastguard Worker        node = self.reverse_call_graph
203*288bf522SAndroid Build Coastguard Worker        for item in callstack:
204*288bf522SAndroid Build Coastguard Worker            node = node.get_child(item[1])
205*288bf522SAndroid Build Coastguard Worker        node.event_count += event_count
206*288bf522SAndroid Build Coastguard Worker
207*288bf522SAndroid Build Coastguard Worker    def update_subtree_event_count(self):
208*288bf522SAndroid Build Coastguard Worker        self.call_graph.update_subtree_event_count()
209*288bf522SAndroid Build Coastguard Worker        self.reverse_call_graph.update_subtree_event_count()
210*288bf522SAndroid Build Coastguard Worker
211*288bf522SAndroid Build Coastguard Worker    def limit_percents(self, min_func_limit: float, min_callchain_percent: float,
212*288bf522SAndroid Build Coastguard Worker                       hit_func_ids: Set[int]):
213*288bf522SAndroid Build Coastguard Worker        for lib in self.libs.values():
214*288bf522SAndroid Build Coastguard Worker            to_del_funcs = []
215*288bf522SAndroid Build Coastguard Worker            for function in lib.functions.values():
216*288bf522SAndroid Build Coastguard Worker                if function.subtree_event_count < min_func_limit:
217*288bf522SAndroid Build Coastguard Worker                    to_del_funcs.append(function.func_id)
218*288bf522SAndroid Build Coastguard Worker                else:
219*288bf522SAndroid Build Coastguard Worker                    hit_func_ids.add(function.func_id)
220*288bf522SAndroid Build Coastguard Worker            for func_id in to_del_funcs:
221*288bf522SAndroid Build Coastguard Worker                del lib.functions[func_id]
222*288bf522SAndroid Build Coastguard Worker        min_limit = min_callchain_percent * 0.01 * self.call_graph.subtree_event_count
223*288bf522SAndroid Build Coastguard Worker        self.call_graph.cut_edge(min_limit, hit_func_ids)
224*288bf522SAndroid Build Coastguard Worker        self.reverse_call_graph.cut_edge(min_limit, hit_func_ids)
225*288bf522SAndroid Build Coastguard Worker
226*288bf522SAndroid Build Coastguard Worker    def get_sample_info(self, gen_addr_hit_map: bool) -> Dict[str, Any]:
227*288bf522SAndroid Build Coastguard Worker        result = {}
228*288bf522SAndroid Build Coastguard Worker        result['tid'] = self.tid
229*288bf522SAndroid Build Coastguard Worker        result['eventCount'] = self.event_count
230*288bf522SAndroid Build Coastguard Worker        result['sampleCount'] = self.sample_count
231*288bf522SAndroid Build Coastguard Worker        result['libs'] = [lib.gen_sample_info(gen_addr_hit_map)
232*288bf522SAndroid Build Coastguard Worker                          for lib in self.libs.values()]
233*288bf522SAndroid Build Coastguard Worker        result['g'] = self.call_graph.gen_sample_info()
234*288bf522SAndroid Build Coastguard Worker        result['rg'] = self.reverse_call_graph.gen_sample_info()
235*288bf522SAndroid Build Coastguard Worker        return result
236*288bf522SAndroid Build Coastguard Worker
237*288bf522SAndroid Build Coastguard Worker    def merge(self, thread: ThreadScope):
238*288bf522SAndroid Build Coastguard Worker        self.event_count += thread.event_count
239*288bf522SAndroid Build Coastguard Worker        self.sample_count += thread.sample_count
240*288bf522SAndroid Build Coastguard Worker        for lib_id, lib in thread.libs.items():
241*288bf522SAndroid Build Coastguard Worker            cur_lib = self.libs.get(lib_id)
242*288bf522SAndroid Build Coastguard Worker            if cur_lib is None:
243*288bf522SAndroid Build Coastguard Worker                self.libs[lib_id] = lib
244*288bf522SAndroid Build Coastguard Worker            else:
245*288bf522SAndroid Build Coastguard Worker                cur_lib.merge(lib)
246*288bf522SAndroid Build Coastguard Worker        self.call_graph.merge(thread.call_graph)
247*288bf522SAndroid Build Coastguard Worker        self.reverse_call_graph.merge(thread.reverse_call_graph)
248*288bf522SAndroid Build Coastguard Worker
249*288bf522SAndroid Build Coastguard Worker    def sort_call_graph_by_function_name(self, get_func_name: Callable[[int], str]) -> None:
250*288bf522SAndroid Build Coastguard Worker        self.call_graph.sort_by_function_name(get_func_name)
251*288bf522SAndroid Build Coastguard Worker        self.reverse_call_graph.sort_by_function_name(get_func_name)
252*288bf522SAndroid Build Coastguard Worker
253*288bf522SAndroid Build Coastguard Worker
254*288bf522SAndroid Build Coastguard Workerclass LibScope(object):
255*288bf522SAndroid Build Coastguard Worker
256*288bf522SAndroid Build Coastguard Worker    def __init__(self, lib_id: int):
257*288bf522SAndroid Build Coastguard Worker        self.lib_id = lib_id
258*288bf522SAndroid Build Coastguard Worker        self.event_count = 0
259*288bf522SAndroid Build Coastguard Worker        self.functions: Dict[int, FunctionScope] = {}  # map from func_id to FunctionScope.
260*288bf522SAndroid Build Coastguard Worker
261*288bf522SAndroid Build Coastguard Worker    def get_function(self, func_id: int) -> FunctionScope:
262*288bf522SAndroid Build Coastguard Worker        function = self.functions.get(func_id)
263*288bf522SAndroid Build Coastguard Worker        if not function:
264*288bf522SAndroid Build Coastguard Worker            function = self.functions[func_id] = FunctionScope(func_id)
265*288bf522SAndroid Build Coastguard Worker        return function
266*288bf522SAndroid Build Coastguard Worker
267*288bf522SAndroid Build Coastguard Worker    def gen_sample_info(self, gen_addr_hit_map: bool) -> Dict[str, Any]:
268*288bf522SAndroid Build Coastguard Worker        result = {}
269*288bf522SAndroid Build Coastguard Worker        result['libId'] = self.lib_id
270*288bf522SAndroid Build Coastguard Worker        result['eventCount'] = self.event_count
271*288bf522SAndroid Build Coastguard Worker        result['functions'] = [func.gen_sample_info(gen_addr_hit_map)
272*288bf522SAndroid Build Coastguard Worker                               for func in self.functions.values()]
273*288bf522SAndroid Build Coastguard Worker        return result
274*288bf522SAndroid Build Coastguard Worker
275*288bf522SAndroid Build Coastguard Worker    def merge(self, lib: LibScope):
276*288bf522SAndroid Build Coastguard Worker        self.event_count += lib.event_count
277*288bf522SAndroid Build Coastguard Worker        for func_id, function in lib.functions.items():
278*288bf522SAndroid Build Coastguard Worker            cur_function = self.functions.get(func_id)
279*288bf522SAndroid Build Coastguard Worker            if cur_function is None:
280*288bf522SAndroid Build Coastguard Worker                self.functions[func_id] = function
281*288bf522SAndroid Build Coastguard Worker            else:
282*288bf522SAndroid Build Coastguard Worker                cur_function.merge(function)
283*288bf522SAndroid Build Coastguard Worker
284*288bf522SAndroid Build Coastguard Worker
285*288bf522SAndroid Build Coastguard Workerclass FunctionScope(object):
286*288bf522SAndroid Build Coastguard Worker
287*288bf522SAndroid Build Coastguard Worker    def __init__(self, func_id: int):
288*288bf522SAndroid Build Coastguard Worker        self.func_id = func_id
289*288bf522SAndroid Build Coastguard Worker        self.sample_count = 0
290*288bf522SAndroid Build Coastguard Worker        self.event_count = 0
291*288bf522SAndroid Build Coastguard Worker        self.subtree_event_count = 0
292*288bf522SAndroid Build Coastguard Worker        self.addr_hit_map = None  # map from addr to [event_count, subtree_event_count].
293*288bf522SAndroid Build Coastguard Worker        # map from (source_file_id, line) to [event_count, subtree_event_count].
294*288bf522SAndroid Build Coastguard Worker        self.line_hit_map = None
295*288bf522SAndroid Build Coastguard Worker
296*288bf522SAndroid Build Coastguard Worker    def build_addr_hit_map(self, addr: int, event_count: int, subtree_event_count: int):
297*288bf522SAndroid Build Coastguard Worker        if self.addr_hit_map is None:
298*288bf522SAndroid Build Coastguard Worker            self.addr_hit_map = {}
299*288bf522SAndroid Build Coastguard Worker        count_info = self.addr_hit_map.get(addr)
300*288bf522SAndroid Build Coastguard Worker        if count_info is None:
301*288bf522SAndroid Build Coastguard Worker            self.addr_hit_map[addr] = [event_count, subtree_event_count]
302*288bf522SAndroid Build Coastguard Worker        else:
303*288bf522SAndroid Build Coastguard Worker            count_info[0] += event_count
304*288bf522SAndroid Build Coastguard Worker            count_info[1] += subtree_event_count
305*288bf522SAndroid Build Coastguard Worker
306*288bf522SAndroid Build Coastguard Worker    def build_line_hit_map(self, source_file_id: int, line: int, event_count: int,
307*288bf522SAndroid Build Coastguard Worker                           subtree_event_count: int):
308*288bf522SAndroid Build Coastguard Worker        if self.line_hit_map is None:
309*288bf522SAndroid Build Coastguard Worker            self.line_hit_map = {}
310*288bf522SAndroid Build Coastguard Worker        key = (source_file_id, line)
311*288bf522SAndroid Build Coastguard Worker        count_info = self.line_hit_map.get(key)
312*288bf522SAndroid Build Coastguard Worker        if count_info is None:
313*288bf522SAndroid Build Coastguard Worker            self.line_hit_map[key] = [event_count, subtree_event_count]
314*288bf522SAndroid Build Coastguard Worker        else:
315*288bf522SAndroid Build Coastguard Worker            count_info[0] += event_count
316*288bf522SAndroid Build Coastguard Worker            count_info[1] += subtree_event_count
317*288bf522SAndroid Build Coastguard Worker
318*288bf522SAndroid Build Coastguard Worker    def gen_sample_info(self, gen_addr_hit_map: bool) -> Dict[str, Any]:
319*288bf522SAndroid Build Coastguard Worker        result = {}
320*288bf522SAndroid Build Coastguard Worker        result['f'] = self.func_id
321*288bf522SAndroid Build Coastguard Worker        result['c'] = [self.sample_count, self.event_count, self.subtree_event_count]
322*288bf522SAndroid Build Coastguard Worker        if self.line_hit_map:
323*288bf522SAndroid Build Coastguard Worker            items = []
324*288bf522SAndroid Build Coastguard Worker            for key in self.line_hit_map:
325*288bf522SAndroid Build Coastguard Worker                count_info = self.line_hit_map[key]
326*288bf522SAndroid Build Coastguard Worker                item = {'f': key[0], 'l': key[1], 'e': count_info[0], 's': count_info[1]}
327*288bf522SAndroid Build Coastguard Worker                items.append(item)
328*288bf522SAndroid Build Coastguard Worker            result['s'] = items
329*288bf522SAndroid Build Coastguard Worker        if gen_addr_hit_map and self.addr_hit_map:
330*288bf522SAndroid Build Coastguard Worker            items = []
331*288bf522SAndroid Build Coastguard Worker            for addr in sorted(self.addr_hit_map):
332*288bf522SAndroid Build Coastguard Worker                count_info = self.addr_hit_map[addr]
333*288bf522SAndroid Build Coastguard Worker                items.append(
334*288bf522SAndroid Build Coastguard Worker                    {'a': hex_address_for_json(addr),
335*288bf522SAndroid Build Coastguard Worker                     'e': count_info[0],
336*288bf522SAndroid Build Coastguard Worker                     's': count_info[1]})
337*288bf522SAndroid Build Coastguard Worker            result['a'] = items
338*288bf522SAndroid Build Coastguard Worker        return result
339*288bf522SAndroid Build Coastguard Worker
340*288bf522SAndroid Build Coastguard Worker    def merge(self, function: FunctionScope):
341*288bf522SAndroid Build Coastguard Worker        self.sample_count += function.sample_count
342*288bf522SAndroid Build Coastguard Worker        self.event_count += function.event_count
343*288bf522SAndroid Build Coastguard Worker        self.subtree_event_count += function.subtree_event_count
344*288bf522SAndroid Build Coastguard Worker        self.addr_hit_map = self.__merge_hit_map(self.addr_hit_map, function.addr_hit_map)
345*288bf522SAndroid Build Coastguard Worker        self.line_hit_map = self.__merge_hit_map(self.line_hit_map, function.line_hit_map)
346*288bf522SAndroid Build Coastguard Worker
347*288bf522SAndroid Build Coastguard Worker    @staticmethod
348*288bf522SAndroid Build Coastguard Worker    def __merge_hit_map(map1: Optional[Dict[int, List[int]]],
349*288bf522SAndroid Build Coastguard Worker                        map2: Optional[Dict[int, List[int]]]) -> Optional[Dict[int, List[int]]]:
350*288bf522SAndroid Build Coastguard Worker        if not map1:
351*288bf522SAndroid Build Coastguard Worker            return map2
352*288bf522SAndroid Build Coastguard Worker        if not map2:
353*288bf522SAndroid Build Coastguard Worker            return map1
354*288bf522SAndroid Build Coastguard Worker        for key, value2 in map2.items():
355*288bf522SAndroid Build Coastguard Worker            value1 = map1.get(key)
356*288bf522SAndroid Build Coastguard Worker            if value1 is None:
357*288bf522SAndroid Build Coastguard Worker                map1[key] = value2
358*288bf522SAndroid Build Coastguard Worker            else:
359*288bf522SAndroid Build Coastguard Worker                value1[0] += value2[0]
360*288bf522SAndroid Build Coastguard Worker                value1[1] += value2[1]
361*288bf522SAndroid Build Coastguard Worker        return map1
362*288bf522SAndroid Build Coastguard Worker
363*288bf522SAndroid Build Coastguard Worker
364*288bf522SAndroid Build Coastguard Workerclass CallNode(object):
365*288bf522SAndroid Build Coastguard Worker
366*288bf522SAndroid Build Coastguard Worker    def __init__(self, func_id: int):
367*288bf522SAndroid Build Coastguard Worker        self.event_count = 0
368*288bf522SAndroid Build Coastguard Worker        self.subtree_event_count = 0
369*288bf522SAndroid Build Coastguard Worker        self.func_id = func_id
370*288bf522SAndroid Build Coastguard Worker        # map from func_id to CallNode
371*288bf522SAndroid Build Coastguard Worker        self.children: Dict[int, CallNode] = collections.OrderedDict()
372*288bf522SAndroid Build Coastguard Worker
373*288bf522SAndroid Build Coastguard Worker    def get_child(self, func_id: int) -> CallNode:
374*288bf522SAndroid Build Coastguard Worker        child = self.children.get(func_id)
375*288bf522SAndroid Build Coastguard Worker        if not child:
376*288bf522SAndroid Build Coastguard Worker            child = self.children[func_id] = CallNode(func_id)
377*288bf522SAndroid Build Coastguard Worker        return child
378*288bf522SAndroid Build Coastguard Worker
379*288bf522SAndroid Build Coastguard Worker    def update_subtree_event_count(self):
380*288bf522SAndroid Build Coastguard Worker        self.subtree_event_count = self.event_count
381*288bf522SAndroid Build Coastguard Worker        for child in self.children.values():
382*288bf522SAndroid Build Coastguard Worker            self.subtree_event_count += child.update_subtree_event_count()
383*288bf522SAndroid Build Coastguard Worker        return self.subtree_event_count
384*288bf522SAndroid Build Coastguard Worker
385*288bf522SAndroid Build Coastguard Worker    def cut_edge(self, min_limit: float, hit_func_ids: Set[int]):
386*288bf522SAndroid Build Coastguard Worker        hit_func_ids.add(self.func_id)
387*288bf522SAndroid Build Coastguard Worker        to_del_children = []
388*288bf522SAndroid Build Coastguard Worker        for key in self.children:
389*288bf522SAndroid Build Coastguard Worker            child = self.children[key]
390*288bf522SAndroid Build Coastguard Worker            if child.subtree_event_count < min_limit:
391*288bf522SAndroid Build Coastguard Worker                to_del_children.append(key)
392*288bf522SAndroid Build Coastguard Worker            else:
393*288bf522SAndroid Build Coastguard Worker                child.cut_edge(min_limit, hit_func_ids)
394*288bf522SAndroid Build Coastguard Worker        for key in to_del_children:
395*288bf522SAndroid Build Coastguard Worker            del self.children[key]
396*288bf522SAndroid Build Coastguard Worker
397*288bf522SAndroid Build Coastguard Worker    def gen_sample_info(self) -> Dict[str, Any]:
398*288bf522SAndroid Build Coastguard Worker        result = {}
399*288bf522SAndroid Build Coastguard Worker        result['e'] = self.event_count
400*288bf522SAndroid Build Coastguard Worker        result['s'] = self.subtree_event_count
401*288bf522SAndroid Build Coastguard Worker        result['f'] = self.func_id
402*288bf522SAndroid Build Coastguard Worker        result['c'] = [child.gen_sample_info() for child in self.children.values()]
403*288bf522SAndroid Build Coastguard Worker        return result
404*288bf522SAndroid Build Coastguard Worker
405*288bf522SAndroid Build Coastguard Worker    def merge(self, node: CallNode):
406*288bf522SAndroid Build Coastguard Worker        self.event_count += node.event_count
407*288bf522SAndroid Build Coastguard Worker        self.subtree_event_count += node.subtree_event_count
408*288bf522SAndroid Build Coastguard Worker        for key, child in node.children.items():
409*288bf522SAndroid Build Coastguard Worker            cur_child = self.children.get(key)
410*288bf522SAndroid Build Coastguard Worker            if cur_child is None:
411*288bf522SAndroid Build Coastguard Worker                self.children[key] = child
412*288bf522SAndroid Build Coastguard Worker            else:
413*288bf522SAndroid Build Coastguard Worker                cur_child.merge(child)
414*288bf522SAndroid Build Coastguard Worker
415*288bf522SAndroid Build Coastguard Worker    def sort_by_function_name(self, get_func_name: Callable[[int], str]) -> None:
416*288bf522SAndroid Build Coastguard Worker        if self.children:
417*288bf522SAndroid Build Coastguard Worker            child_func_ids = list(self.children.keys())
418*288bf522SAndroid Build Coastguard Worker            child_func_ids.sort(key=get_func_name)
419*288bf522SAndroid Build Coastguard Worker            new_children = collections.OrderedDict()
420*288bf522SAndroid Build Coastguard Worker            for func_id in child_func_ids:
421*288bf522SAndroid Build Coastguard Worker                new_children[func_id] = self.children[func_id]
422*288bf522SAndroid Build Coastguard Worker            self.children = new_children
423*288bf522SAndroid Build Coastguard Worker            for child in self.children.values():
424*288bf522SAndroid Build Coastguard Worker                child.sort_by_function_name(get_func_name)
425*288bf522SAndroid Build Coastguard Worker
426*288bf522SAndroid Build Coastguard Worker
427*288bf522SAndroid Build Coastguard Worker@dataclass
428*288bf522SAndroid Build Coastguard Workerclass LibInfo:
429*288bf522SAndroid Build Coastguard Worker    name: str
430*288bf522SAndroid Build Coastguard Worker    build_id: str
431*288bf522SAndroid Build Coastguard Worker
432*288bf522SAndroid Build Coastguard Worker
433*288bf522SAndroid Build Coastguard Workerclass LibSet(object):
434*288bf522SAndroid Build Coastguard Worker    """ Collection of shared libraries used in perf.data. """
435*288bf522SAndroid Build Coastguard Worker
436*288bf522SAndroid Build Coastguard Worker    def __init__(self):
437*288bf522SAndroid Build Coastguard Worker        self.lib_name_to_id: Dict[str, int] = {}
438*288bf522SAndroid Build Coastguard Worker        self.libs: List[LibInfo] = []
439*288bf522SAndroid Build Coastguard Worker
440*288bf522SAndroid Build Coastguard Worker    def get_lib_id(self, lib_name: str) -> Optional[int]:
441*288bf522SAndroid Build Coastguard Worker        return self.lib_name_to_id.get(lib_name)
442*288bf522SAndroid Build Coastguard Worker
443*288bf522SAndroid Build Coastguard Worker    def add_lib(self, lib_name: str, build_id: str) -> int:
444*288bf522SAndroid Build Coastguard Worker        """ Return lib_id of the newly added lib. """
445*288bf522SAndroid Build Coastguard Worker        lib_id = len(self.libs)
446*288bf522SAndroid Build Coastguard Worker        self.libs.append(LibInfo(lib_name, build_id))
447*288bf522SAndroid Build Coastguard Worker        self.lib_name_to_id[lib_name] = lib_id
448*288bf522SAndroid Build Coastguard Worker        return lib_id
449*288bf522SAndroid Build Coastguard Worker
450*288bf522SAndroid Build Coastguard Worker    def get_lib(self, lib_id: int) -> LibInfo:
451*288bf522SAndroid Build Coastguard Worker        return self.libs[lib_id]
452*288bf522SAndroid Build Coastguard Worker
453*288bf522SAndroid Build Coastguard Worker
454*288bf522SAndroid Build Coastguard Workerclass Function(object):
455*288bf522SAndroid Build Coastguard Worker    """ Represent a function in a shared library. """
456*288bf522SAndroid Build Coastguard Worker
457*288bf522SAndroid Build Coastguard Worker    def __init__(self, lib_id: int, func_name: str, func_id: int, start_addr: int, addr_len: int):
458*288bf522SAndroid Build Coastguard Worker        self.lib_id = lib_id
459*288bf522SAndroid Build Coastguard Worker        self.func_name = func_name
460*288bf522SAndroid Build Coastguard Worker        self.func_id = func_id
461*288bf522SAndroid Build Coastguard Worker        self.start_addr = start_addr
462*288bf522SAndroid Build Coastguard Worker        self.addr_len = addr_len
463*288bf522SAndroid Build Coastguard Worker        self.source_info = None
464*288bf522SAndroid Build Coastguard Worker        self.disassembly = None
465*288bf522SAndroid Build Coastguard Worker
466*288bf522SAndroid Build Coastguard Worker
467*288bf522SAndroid Build Coastguard Workerclass FunctionSet(object):
468*288bf522SAndroid Build Coastguard Worker    """ Collection of functions used in perf.data. """
469*288bf522SAndroid Build Coastguard Worker
470*288bf522SAndroid Build Coastguard Worker    def __init__(self):
471*288bf522SAndroid Build Coastguard Worker        self.name_to_func: Dict[Tuple[int, str], Function] = {}
472*288bf522SAndroid Build Coastguard Worker        self.id_to_func: Dict[int, Function] = {}
473*288bf522SAndroid Build Coastguard Worker
474*288bf522SAndroid Build Coastguard Worker    def get_func_id(self, lib_id: int, symbol: SymbolStruct) -> int:
475*288bf522SAndroid Build Coastguard Worker        key = (lib_id, symbol.symbol_name)
476*288bf522SAndroid Build Coastguard Worker        function = self.name_to_func.get(key)
477*288bf522SAndroid Build Coastguard Worker        if function is None:
478*288bf522SAndroid Build Coastguard Worker            func_id = len(self.id_to_func)
479*288bf522SAndroid Build Coastguard Worker            function = Function(lib_id, symbol.symbol_name, func_id, symbol.symbol_addr,
480*288bf522SAndroid Build Coastguard Worker                                symbol.symbol_len)
481*288bf522SAndroid Build Coastguard Worker            self.name_to_func[key] = function
482*288bf522SAndroid Build Coastguard Worker            self.id_to_func[func_id] = function
483*288bf522SAndroid Build Coastguard Worker        return function.func_id
484*288bf522SAndroid Build Coastguard Worker
485*288bf522SAndroid Build Coastguard Worker    def get_func_name(self, func_id: int) -> str:
486*288bf522SAndroid Build Coastguard Worker        return self.id_to_func[func_id].func_name
487*288bf522SAndroid Build Coastguard Worker
488*288bf522SAndroid Build Coastguard Worker    def trim_functions(self, left_func_ids: Set[int]):
489*288bf522SAndroid Build Coastguard Worker        """ Remove functions excepts those in left_func_ids. """
490*288bf522SAndroid Build Coastguard Worker        for function in self.name_to_func.values():
491*288bf522SAndroid Build Coastguard Worker            if function.func_id not in left_func_ids:
492*288bf522SAndroid Build Coastguard Worker                del self.id_to_func[function.func_id]
493*288bf522SAndroid Build Coastguard Worker        # name_to_func will not be used.
494*288bf522SAndroid Build Coastguard Worker        self.name_to_func = None
495*288bf522SAndroid Build Coastguard Worker
496*288bf522SAndroid Build Coastguard Worker
497*288bf522SAndroid Build Coastguard Workerclass SourceFile(object):
498*288bf522SAndroid Build Coastguard Worker    """ A source file containing source code hit by samples. """
499*288bf522SAndroid Build Coastguard Worker
500*288bf522SAndroid Build Coastguard Worker    def __init__(self, file_id: int, abstract_path: str):
501*288bf522SAndroid Build Coastguard Worker        self.file_id = file_id
502*288bf522SAndroid Build Coastguard Worker        self.abstract_path = abstract_path  # path reported by addr2line
503*288bf522SAndroid Build Coastguard Worker        self.real_path: Optional[str] = None  # file path in the file system
504*288bf522SAndroid Build Coastguard Worker        self.requested_lines: Optional[Set[int]] = set()
505*288bf522SAndroid Build Coastguard Worker        self.line_to_code: Dict[int, str] = {}  # map from line to code in that line.
506*288bf522SAndroid Build Coastguard Worker
507*288bf522SAndroid Build Coastguard Worker    def request_lines(self, start_line: int, end_line: int):
508*288bf522SAndroid Build Coastguard Worker        self.requested_lines |= set(range(start_line, end_line + 1))
509*288bf522SAndroid Build Coastguard Worker
510*288bf522SAndroid Build Coastguard Worker    def add_source_code(self, real_path: str):
511*288bf522SAndroid Build Coastguard Worker        self.real_path = real_path
512*288bf522SAndroid Build Coastguard Worker        with open(real_path, 'r') as f:
513*288bf522SAndroid Build Coastguard Worker            source_code = f.readlines()
514*288bf522SAndroid Build Coastguard Worker        max_line = len(source_code)
515*288bf522SAndroid Build Coastguard Worker        for line in self.requested_lines:
516*288bf522SAndroid Build Coastguard Worker            if line > 0 and line <= max_line:
517*288bf522SAndroid Build Coastguard Worker                self.line_to_code[line] = source_code[line - 1]
518*288bf522SAndroid Build Coastguard Worker        # requested_lines is no longer used.
519*288bf522SAndroid Build Coastguard Worker        self.requested_lines = None
520*288bf522SAndroid Build Coastguard Worker
521*288bf522SAndroid Build Coastguard Worker
522*288bf522SAndroid Build Coastguard Workerclass SourceFileSet(object):
523*288bf522SAndroid Build Coastguard Worker    """ Collection of source files. """
524*288bf522SAndroid Build Coastguard Worker
525*288bf522SAndroid Build Coastguard Worker    def __init__(self):
526*288bf522SAndroid Build Coastguard Worker        self.path_to_source_files: Dict[str, SourceFile] = {}  # map from file path to SourceFile.
527*288bf522SAndroid Build Coastguard Worker
528*288bf522SAndroid Build Coastguard Worker    def get_source_file(self, file_path: str) -> SourceFile:
529*288bf522SAndroid Build Coastguard Worker        source_file = self.path_to_source_files.get(file_path)
530*288bf522SAndroid Build Coastguard Worker        if not source_file:
531*288bf522SAndroid Build Coastguard Worker            source_file = SourceFile(len(self.path_to_source_files), file_path)
532*288bf522SAndroid Build Coastguard Worker            self.path_to_source_files[file_path] = source_file
533*288bf522SAndroid Build Coastguard Worker        return source_file
534*288bf522SAndroid Build Coastguard Worker
535*288bf522SAndroid Build Coastguard Worker    def load_source_code(self, source_dirs: List[str]):
536*288bf522SAndroid Build Coastguard Worker        file_searcher = SourceFileSearcher(source_dirs)
537*288bf522SAndroid Build Coastguard Worker        for source_file in self.path_to_source_files.values():
538*288bf522SAndroid Build Coastguard Worker            real_path = file_searcher.get_real_path(source_file.abstract_path)
539*288bf522SAndroid Build Coastguard Worker            if real_path:
540*288bf522SAndroid Build Coastguard Worker                source_file.add_source_code(real_path)
541*288bf522SAndroid Build Coastguard Worker
542*288bf522SAndroid Build Coastguard Worker
543*288bf522SAndroid Build Coastguard Workerclass RecordData(object):
544*288bf522SAndroid Build Coastguard Worker
545*288bf522SAndroid Build Coastguard Worker    """RecordData reads perf.data, and generates data used by report_html.js in json format.
546*288bf522SAndroid Build Coastguard Worker        All generated items are listed as below:
547*288bf522SAndroid Build Coastguard Worker            1. recordTime: string
548*288bf522SAndroid Build Coastguard Worker            2. machineType: string
549*288bf522SAndroid Build Coastguard Worker            3. androidVersion: string
550*288bf522SAndroid Build Coastguard Worker            4. recordCmdline: string
551*288bf522SAndroid Build Coastguard Worker            5. totalSamples: int
552*288bf522SAndroid Build Coastguard Worker            6. processNames: map from pid to processName.
553*288bf522SAndroid Build Coastguard Worker            7. threadNames: map from tid to threadName.
554*288bf522SAndroid Build Coastguard Worker            8. libList: an array of libNames, indexed by libId.
555*288bf522SAndroid Build Coastguard Worker            9. functionMap: map from functionId to funcData.
556*288bf522SAndroid Build Coastguard Worker                funcData = {
557*288bf522SAndroid Build Coastguard Worker                    l: libId
558*288bf522SAndroid Build Coastguard Worker                    f: functionName
559*288bf522SAndroid Build Coastguard Worker                    s: [sourceFileId, startLine, endLine] [optional]
560*288bf522SAndroid Build Coastguard Worker                    d: [(disassembly, addr)] [optional]
561*288bf522SAndroid Build Coastguard Worker                }
562*288bf522SAndroid Build Coastguard Worker
563*288bf522SAndroid Build Coastguard Worker            10.  sampleInfo = [eventInfo]
564*288bf522SAndroid Build Coastguard Worker                eventInfo = {
565*288bf522SAndroid Build Coastguard Worker                    eventName
566*288bf522SAndroid Build Coastguard Worker                    eventCount
567*288bf522SAndroid Build Coastguard Worker                    processes: [processInfo]
568*288bf522SAndroid Build Coastguard Worker                }
569*288bf522SAndroid Build Coastguard Worker                processInfo = {
570*288bf522SAndroid Build Coastguard Worker                    pid
571*288bf522SAndroid Build Coastguard Worker                    eventCount
572*288bf522SAndroid Build Coastguard Worker                    threads: [threadInfo]
573*288bf522SAndroid Build Coastguard Worker                }
574*288bf522SAndroid Build Coastguard Worker                threadInfo = {
575*288bf522SAndroid Build Coastguard Worker                    tid
576*288bf522SAndroid Build Coastguard Worker                    eventCount
577*288bf522SAndroid Build Coastguard Worker                    sampleCount
578*288bf522SAndroid Build Coastguard Worker                    libs: [libInfo],
579*288bf522SAndroid Build Coastguard Worker                    g: callGraph,
580*288bf522SAndroid Build Coastguard Worker                    rg: reverseCallgraph
581*288bf522SAndroid Build Coastguard Worker                }
582*288bf522SAndroid Build Coastguard Worker                libInfo = {
583*288bf522SAndroid Build Coastguard Worker                    libId,
584*288bf522SAndroid Build Coastguard Worker                    eventCount,
585*288bf522SAndroid Build Coastguard Worker                    functions: [funcInfo]
586*288bf522SAndroid Build Coastguard Worker                }
587*288bf522SAndroid Build Coastguard Worker                funcInfo = {
588*288bf522SAndroid Build Coastguard Worker                    f: functionId
589*288bf522SAndroid Build Coastguard Worker                    c: [sampleCount, eventCount, subTreeEventCount]
590*288bf522SAndroid Build Coastguard Worker                    s: [sourceCodeInfo] [optional]
591*288bf522SAndroid Build Coastguard Worker                    a: [addrInfo] (sorted by addrInfo.addr) [optional]
592*288bf522SAndroid Build Coastguard Worker                }
593*288bf522SAndroid Build Coastguard Worker                callGraph and reverseCallGraph are both of type CallNode.
594*288bf522SAndroid Build Coastguard Worker                callGraph shows how a function calls other functions.
595*288bf522SAndroid Build Coastguard Worker                reverseCallGraph shows how a function is called by other functions.
596*288bf522SAndroid Build Coastguard Worker                CallNode {
597*288bf522SAndroid Build Coastguard Worker                    e: selfEventCount
598*288bf522SAndroid Build Coastguard Worker                    s: subTreeEventCount
599*288bf522SAndroid Build Coastguard Worker                    f: functionId
600*288bf522SAndroid Build Coastguard Worker                    c: [CallNode] # children
601*288bf522SAndroid Build Coastguard Worker                }
602*288bf522SAndroid Build Coastguard Worker
603*288bf522SAndroid Build Coastguard Worker                sourceCodeInfo {
604*288bf522SAndroid Build Coastguard Worker                    f: sourceFileId
605*288bf522SAndroid Build Coastguard Worker                    l: line
606*288bf522SAndroid Build Coastguard Worker                    e: eventCount
607*288bf522SAndroid Build Coastguard Worker                    s: subtreeEventCount
608*288bf522SAndroid Build Coastguard Worker                }
609*288bf522SAndroid Build Coastguard Worker
610*288bf522SAndroid Build Coastguard Worker                addrInfo {
611*288bf522SAndroid Build Coastguard Worker                    a: addr
612*288bf522SAndroid Build Coastguard Worker                    e: eventCount
613*288bf522SAndroid Build Coastguard Worker                    s: subtreeEventCount
614*288bf522SAndroid Build Coastguard Worker                }
615*288bf522SAndroid Build Coastguard Worker
616*288bf522SAndroid Build Coastguard Worker            11. sourceFiles: an array of sourceFile, indexed by sourceFileId.
617*288bf522SAndroid Build Coastguard Worker                sourceFile {
618*288bf522SAndroid Build Coastguard Worker                    path
619*288bf522SAndroid Build Coastguard Worker                    code:  # a map from line to code for that line.
620*288bf522SAndroid Build Coastguard Worker                }
621*288bf522SAndroid Build Coastguard Worker    """
622*288bf522SAndroid Build Coastguard Worker
623*288bf522SAndroid Build Coastguard Worker    def __init__(
624*288bf522SAndroid Build Coastguard Worker            self, binary_cache_path: Optional[str],
625*288bf522SAndroid Build Coastguard Worker            ndk_path: Optional[str],
626*288bf522SAndroid Build Coastguard Worker            build_addr_hit_map: bool):
627*288bf522SAndroid Build Coastguard Worker        self.binary_cache_path = binary_cache_path
628*288bf522SAndroid Build Coastguard Worker        self.ndk_path = ndk_path
629*288bf522SAndroid Build Coastguard Worker        self.build_addr_hit_map = build_addr_hit_map
630*288bf522SAndroid Build Coastguard Worker        self.meta_info: Optional[Dict[str, str]] = None
631*288bf522SAndroid Build Coastguard Worker        self.cmdline: Optional[str] = None
632*288bf522SAndroid Build Coastguard Worker        self.arch: Optional[str] = None
633*288bf522SAndroid Build Coastguard Worker        self.events: Dict[str, EventScope] = {}
634*288bf522SAndroid Build Coastguard Worker        self.libs = LibSet()
635*288bf522SAndroid Build Coastguard Worker        self.functions = FunctionSet()
636*288bf522SAndroid Build Coastguard Worker        self.total_samples = 0
637*288bf522SAndroid Build Coastguard Worker        self.source_files = SourceFileSet()
638*288bf522SAndroid Build Coastguard Worker        self.gen_addr_hit_map_in_record_info = False
639*288bf522SAndroid Build Coastguard Worker        self.binary_finder = BinaryFinder(binary_cache_path, ReadElf(ndk_path))
640*288bf522SAndroid Build Coastguard Worker
641*288bf522SAndroid Build Coastguard Worker    def load_record_file(self, record_file: str, report_lib_options: ReportLibOptions):
642*288bf522SAndroid Build Coastguard Worker        lib = GetReportLib(record_file)
643*288bf522SAndroid Build Coastguard Worker        # If not showing ip for unknown symbols, the percent of the unknown symbol may be
644*288bf522SAndroid Build Coastguard Worker        # accumulated to very big, and ranks first in the sample table.
645*288bf522SAndroid Build Coastguard Worker        lib.ShowIpForUnknownSymbol()
646*288bf522SAndroid Build Coastguard Worker        if self.binary_cache_path:
647*288bf522SAndroid Build Coastguard Worker            lib.SetSymfs(self.binary_cache_path)
648*288bf522SAndroid Build Coastguard Worker        lib.SetReportOptions(report_lib_options)
649*288bf522SAndroid Build Coastguard Worker        self.meta_info = lib.MetaInfo()
650*288bf522SAndroid Build Coastguard Worker        self.cmdline = lib.GetRecordCmd()
651*288bf522SAndroid Build Coastguard Worker        self.arch = lib.GetArch()
652*288bf522SAndroid Build Coastguard Worker        while True:
653*288bf522SAndroid Build Coastguard Worker            raw_sample = lib.GetNextSample()
654*288bf522SAndroid Build Coastguard Worker            if not raw_sample:
655*288bf522SAndroid Build Coastguard Worker                lib.Close()
656*288bf522SAndroid Build Coastguard Worker                break
657*288bf522SAndroid Build Coastguard Worker            raw_event = lib.GetEventOfCurrentSample()
658*288bf522SAndroid Build Coastguard Worker            symbol = lib.GetSymbolOfCurrentSample()
659*288bf522SAndroid Build Coastguard Worker            callchain = lib.GetCallChainOfCurrentSample()
660*288bf522SAndroid Build Coastguard Worker            event = self._get_event(raw_event.name)
661*288bf522SAndroid Build Coastguard Worker            self.total_samples += 1
662*288bf522SAndroid Build Coastguard Worker            event.sample_count += 1
663*288bf522SAndroid Build Coastguard Worker            event.event_count += raw_sample.period
664*288bf522SAndroid Build Coastguard Worker            process = event.get_process(raw_sample.pid)
665*288bf522SAndroid Build Coastguard Worker            process.event_count += raw_sample.period
666*288bf522SAndroid Build Coastguard Worker            thread = process.get_thread(raw_sample.tid, raw_sample.thread_comm)
667*288bf522SAndroid Build Coastguard Worker            thread.event_count += raw_sample.period
668*288bf522SAndroid Build Coastguard Worker            thread.sample_count += 1
669*288bf522SAndroid Build Coastguard Worker
670*288bf522SAndroid Build Coastguard Worker            lib_id = self.libs.get_lib_id(symbol.dso_name)
671*288bf522SAndroid Build Coastguard Worker            if lib_id is None:
672*288bf522SAndroid Build Coastguard Worker                lib_id = self.libs.add_lib(symbol.dso_name, lib.GetBuildIdForPath(symbol.dso_name))
673*288bf522SAndroid Build Coastguard Worker            func_id = self.functions.get_func_id(lib_id, symbol)
674*288bf522SAndroid Build Coastguard Worker            callstack = [(lib_id, func_id, symbol.vaddr_in_file)]
675*288bf522SAndroid Build Coastguard Worker            for i in range(callchain.nr):
676*288bf522SAndroid Build Coastguard Worker                symbol = callchain.entries[i].symbol
677*288bf522SAndroid Build Coastguard Worker                lib_id = self.libs.get_lib_id(symbol.dso_name)
678*288bf522SAndroid Build Coastguard Worker                if lib_id is None:
679*288bf522SAndroid Build Coastguard Worker                    lib_id = self.libs.add_lib(
680*288bf522SAndroid Build Coastguard Worker                        symbol.dso_name, lib.GetBuildIdForPath(symbol.dso_name))
681*288bf522SAndroid Build Coastguard Worker                func_id = self.functions.get_func_id(lib_id, symbol)
682*288bf522SAndroid Build Coastguard Worker                callstack.append((lib_id, func_id, symbol.vaddr_in_file))
683*288bf522SAndroid Build Coastguard Worker            if len(callstack) > MAX_CALLSTACK_LENGTH:
684*288bf522SAndroid Build Coastguard Worker                callstack = callstack[:MAX_CALLSTACK_LENGTH]
685*288bf522SAndroid Build Coastguard Worker            thread.add_callstack(raw_sample.period, callstack, self.build_addr_hit_map)
686*288bf522SAndroid Build Coastguard Worker
687*288bf522SAndroid Build Coastguard Worker        for event in self.events.values():
688*288bf522SAndroid Build Coastguard Worker            for thread in event.threads:
689*288bf522SAndroid Build Coastguard Worker                thread.update_subtree_event_count()
690*288bf522SAndroid Build Coastguard Worker
691*288bf522SAndroid Build Coastguard Worker    def aggregate_by_thread_name(self):
692*288bf522SAndroid Build Coastguard Worker        for event in self.events.values():
693*288bf522SAndroid Build Coastguard Worker            new_processes = {}  # from process name to ProcessScope
694*288bf522SAndroid Build Coastguard Worker            for process in event.processes.values():
695*288bf522SAndroid Build Coastguard Worker                cur_process = new_processes.get(process.name)
696*288bf522SAndroid Build Coastguard Worker                if cur_process is None:
697*288bf522SAndroid Build Coastguard Worker                    new_processes[process.name] = process
698*288bf522SAndroid Build Coastguard Worker                else:
699*288bf522SAndroid Build Coastguard Worker                    cur_process.merge_by_thread_name(process)
700*288bf522SAndroid Build Coastguard Worker            event.processes = {}
701*288bf522SAndroid Build Coastguard Worker            for process in new_processes.values():
702*288bf522SAndroid Build Coastguard Worker                event.processes[process.pid] = process
703*288bf522SAndroid Build Coastguard Worker
704*288bf522SAndroid Build Coastguard Worker    def limit_percents(self, min_func_percent: float, min_callchain_percent: float):
705*288bf522SAndroid Build Coastguard Worker        hit_func_ids: Set[int] = set()
706*288bf522SAndroid Build Coastguard Worker        for event in self.events.values():
707*288bf522SAndroid Build Coastguard Worker            min_limit = event.event_count * min_func_percent * 0.01
708*288bf522SAndroid Build Coastguard Worker            to_del_processes = []
709*288bf522SAndroid Build Coastguard Worker            for process in event.processes.values():
710*288bf522SAndroid Build Coastguard Worker                to_del_threads = []
711*288bf522SAndroid Build Coastguard Worker                for thread in process.threads.values():
712*288bf522SAndroid Build Coastguard Worker                    if thread.call_graph.subtree_event_count < min_limit:
713*288bf522SAndroid Build Coastguard Worker                        to_del_threads.append(thread.tid)
714*288bf522SAndroid Build Coastguard Worker                    else:
715*288bf522SAndroid Build Coastguard Worker                        thread.limit_percents(min_limit, min_callchain_percent, hit_func_ids)
716*288bf522SAndroid Build Coastguard Worker                for thread in to_del_threads:
717*288bf522SAndroid Build Coastguard Worker                    del process.threads[thread]
718*288bf522SAndroid Build Coastguard Worker                if not process.threads:
719*288bf522SAndroid Build Coastguard Worker                    to_del_processes.append(process.pid)
720*288bf522SAndroid Build Coastguard Worker            for process in to_del_processes:
721*288bf522SAndroid Build Coastguard Worker                del event.processes[process]
722*288bf522SAndroid Build Coastguard Worker        self.functions.trim_functions(hit_func_ids)
723*288bf522SAndroid Build Coastguard Worker
724*288bf522SAndroid Build Coastguard Worker    def sort_call_graph_by_function_name(self) -> None:
725*288bf522SAndroid Build Coastguard Worker        for event in self.events.values():
726*288bf522SAndroid Build Coastguard Worker            for process in event.processes.values():
727*288bf522SAndroid Build Coastguard Worker                for thread in process.threads.values():
728*288bf522SAndroid Build Coastguard Worker                    thread.sort_call_graph_by_function_name(self.functions.get_func_name)
729*288bf522SAndroid Build Coastguard Worker
730*288bf522SAndroid Build Coastguard Worker    def _get_event(self, event_name: str) -> EventScope:
731*288bf522SAndroid Build Coastguard Worker        if event_name not in self.events:
732*288bf522SAndroid Build Coastguard Worker            self.events[event_name] = EventScope(event_name)
733*288bf522SAndroid Build Coastguard Worker        return self.events[event_name]
734*288bf522SAndroid Build Coastguard Worker
735*288bf522SAndroid Build Coastguard Worker    def add_source_code(self, source_dirs: List[str], filter_lib: Callable[[str], bool], jobs: int):
736*288bf522SAndroid Build Coastguard Worker        """ Collect source code information:
737*288bf522SAndroid Build Coastguard Worker            1. Find line ranges for each function in FunctionSet.
738*288bf522SAndroid Build Coastguard Worker            2. Find line for each addr in FunctionScope.addr_hit_map.
739*288bf522SAndroid Build Coastguard Worker            3. Collect needed source code in SourceFileSet.
740*288bf522SAndroid Build Coastguard Worker        """
741*288bf522SAndroid Build Coastguard Worker        addr2line = Addr2Nearestline(self.ndk_path, self.binary_finder, False)
742*288bf522SAndroid Build Coastguard Worker        # Request line range for each function.
743*288bf522SAndroid Build Coastguard Worker        for function in self.functions.id_to_func.values():
744*288bf522SAndroid Build Coastguard Worker            if function.func_name == 'unknown':
745*288bf522SAndroid Build Coastguard Worker                continue
746*288bf522SAndroid Build Coastguard Worker            lib_info = self.libs.get_lib(function.lib_id)
747*288bf522SAndroid Build Coastguard Worker            if filter_lib(lib_info.name):
748*288bf522SAndroid Build Coastguard Worker                addr2line.add_addr(lib_info.name, lib_info.build_id,
749*288bf522SAndroid Build Coastguard Worker                                   function.start_addr, function.start_addr)
750*288bf522SAndroid Build Coastguard Worker                addr2line.add_addr(lib_info.name, lib_info.build_id, function.start_addr,
751*288bf522SAndroid Build Coastguard Worker                                   function.start_addr + function.addr_len - 1)
752*288bf522SAndroid Build Coastguard Worker        # Request line for each addr in FunctionScope.addr_hit_map.
753*288bf522SAndroid Build Coastguard Worker        for event in self.events.values():
754*288bf522SAndroid Build Coastguard Worker            for lib in event.libraries:
755*288bf522SAndroid Build Coastguard Worker                lib_info = self.libs.get_lib(lib.lib_id)
756*288bf522SAndroid Build Coastguard Worker                if filter_lib(lib_info.name):
757*288bf522SAndroid Build Coastguard Worker                    for function in lib.functions.values():
758*288bf522SAndroid Build Coastguard Worker                        func_addr = self.functions.id_to_func[function.func_id].start_addr
759*288bf522SAndroid Build Coastguard Worker                        for addr in function.addr_hit_map:
760*288bf522SAndroid Build Coastguard Worker                            addr2line.add_addr(lib_info.name, lib_info.build_id, func_addr, addr)
761*288bf522SAndroid Build Coastguard Worker        addr2line.convert_addrs_to_lines(jobs)
762*288bf522SAndroid Build Coastguard Worker
763*288bf522SAndroid Build Coastguard Worker        # Set line range for each function.
764*288bf522SAndroid Build Coastguard Worker        for function in self.functions.id_to_func.values():
765*288bf522SAndroid Build Coastguard Worker            if function.func_name == 'unknown':
766*288bf522SAndroid Build Coastguard Worker                continue
767*288bf522SAndroid Build Coastguard Worker            dso = addr2line.get_dso(self.libs.get_lib(function.lib_id).name)
768*288bf522SAndroid Build Coastguard Worker            if not dso:
769*288bf522SAndroid Build Coastguard Worker                continue
770*288bf522SAndroid Build Coastguard Worker            start_source = addr2line.get_addr_source(dso, function.start_addr)
771*288bf522SAndroid Build Coastguard Worker            end_source = addr2line.get_addr_source(dso, function.start_addr + function.addr_len - 1)
772*288bf522SAndroid Build Coastguard Worker            if not start_source or not end_source:
773*288bf522SAndroid Build Coastguard Worker                continue
774*288bf522SAndroid Build Coastguard Worker            start_file_path, start_line = start_source[-1]
775*288bf522SAndroid Build Coastguard Worker            end_file_path, end_line = end_source[-1]
776*288bf522SAndroid Build Coastguard Worker            if start_file_path != end_file_path or start_line > end_line:
777*288bf522SAndroid Build Coastguard Worker                continue
778*288bf522SAndroid Build Coastguard Worker            source_file = self.source_files.get_source_file(start_file_path)
779*288bf522SAndroid Build Coastguard Worker            source_file.request_lines(start_line, end_line)
780*288bf522SAndroid Build Coastguard Worker            function.source_info = (source_file.file_id, start_line, end_line)
781*288bf522SAndroid Build Coastguard Worker
782*288bf522SAndroid Build Coastguard Worker        # Build FunctionScope.line_hit_map.
783*288bf522SAndroid Build Coastguard Worker        for event in self.events.values():
784*288bf522SAndroid Build Coastguard Worker            for lib in event.libraries:
785*288bf522SAndroid Build Coastguard Worker                dso = addr2line.get_dso(self.libs.get_lib(lib.lib_id).name)
786*288bf522SAndroid Build Coastguard Worker                if not dso:
787*288bf522SAndroid Build Coastguard Worker                    continue
788*288bf522SAndroid Build Coastguard Worker                for function in lib.functions.values():
789*288bf522SAndroid Build Coastguard Worker                    for addr in function.addr_hit_map:
790*288bf522SAndroid Build Coastguard Worker                        source = addr2line.get_addr_source(dso, addr)
791*288bf522SAndroid Build Coastguard Worker                        if not source:
792*288bf522SAndroid Build Coastguard Worker                            continue
793*288bf522SAndroid Build Coastguard Worker                        for file_path, line in source:
794*288bf522SAndroid Build Coastguard Worker                            source_file = self.source_files.get_source_file(file_path)
795*288bf522SAndroid Build Coastguard Worker                            # Show [line - 5, line + 5] of the line hit by a sample.
796*288bf522SAndroid Build Coastguard Worker                            source_file.request_lines(line - 5, line + 5)
797*288bf522SAndroid Build Coastguard Worker                            count_info = function.addr_hit_map[addr]
798*288bf522SAndroid Build Coastguard Worker                            function.build_line_hit_map(source_file.file_id, line, count_info[0],
799*288bf522SAndroid Build Coastguard Worker                                                        count_info[1])
800*288bf522SAndroid Build Coastguard Worker
801*288bf522SAndroid Build Coastguard Worker        # Collect needed source code in SourceFileSet.
802*288bf522SAndroid Build Coastguard Worker        self.source_files.load_source_code(source_dirs)
803*288bf522SAndroid Build Coastguard Worker
804*288bf522SAndroid Build Coastguard Worker    def add_disassembly(self, filter_lib: Callable[[str], bool],
805*288bf522SAndroid Build Coastguard Worker                        jobs: int, disassemble_job_size: int):
806*288bf522SAndroid Build Coastguard Worker        """ Collect disassembly information:
807*288bf522SAndroid Build Coastguard Worker            1. Use objdump to collect disassembly for each function in FunctionSet.
808*288bf522SAndroid Build Coastguard Worker            2. Set flag to dump addr_hit_map when generating record info.
809*288bf522SAndroid Build Coastguard Worker        """
810*288bf522SAndroid Build Coastguard Worker        objdump = Objdump(self.ndk_path, self.binary_finder)
811*288bf522SAndroid Build Coastguard Worker        lib_functions: Dict[int, List[Function]] = collections.defaultdict(list)
812*288bf522SAndroid Build Coastguard Worker
813*288bf522SAndroid Build Coastguard Worker        for function in self.functions.id_to_func.values():
814*288bf522SAndroid Build Coastguard Worker            if function.func_name == 'unknown':
815*288bf522SAndroid Build Coastguard Worker                continue
816*288bf522SAndroid Build Coastguard Worker            lib_functions[function.lib_id].append(function)
817*288bf522SAndroid Build Coastguard Worker
818*288bf522SAndroid Build Coastguard Worker        with ThreadPoolExecutor(jobs) as executor:
819*288bf522SAndroid Build Coastguard Worker            futures: List[Future] = []
820*288bf522SAndroid Build Coastguard Worker            all_tasks = []
821*288bf522SAndroid Build Coastguard Worker            for lib_id, functions in lib_functions.items():
822*288bf522SAndroid Build Coastguard Worker                lib = self.libs.get_lib(lib_id)
823*288bf522SAndroid Build Coastguard Worker                if not filter_lib(lib.name):
824*288bf522SAndroid Build Coastguard Worker                    continue
825*288bf522SAndroid Build Coastguard Worker                dso_info = objdump.get_dso_info(lib.name, lib.build_id)
826*288bf522SAndroid Build Coastguard Worker                if not dso_info:
827*288bf522SAndroid Build Coastguard Worker                    continue
828*288bf522SAndroid Build Coastguard Worker
829*288bf522SAndroid Build Coastguard Worker                tasks = self.split_disassembly_jobs(functions, disassemble_job_size)
830*288bf522SAndroid Build Coastguard Worker                logging.debug('create %d jobs to disassemble %d functions in %s',
831*288bf522SAndroid Build Coastguard Worker                              len(tasks), len(functions), lib.name)
832*288bf522SAndroid Build Coastguard Worker                for task in tasks:
833*288bf522SAndroid Build Coastguard Worker                    futures.append(executor.submit(
834*288bf522SAndroid Build Coastguard Worker                        self._disassemble_functions, objdump, dso_info, task))
835*288bf522SAndroid Build Coastguard Worker                    all_tasks.append(task)
836*288bf522SAndroid Build Coastguard Worker
837*288bf522SAndroid Build Coastguard Worker            for task, future in zip(all_tasks, futures):
838*288bf522SAndroid Build Coastguard Worker                result = future.result()
839*288bf522SAndroid Build Coastguard Worker                if result and len(result) == len(task):
840*288bf522SAndroid Build Coastguard Worker                    for function, disassembly in zip(task, result):
841*288bf522SAndroid Build Coastguard Worker                        function.disassembly = disassembly.lines
842*288bf522SAndroid Build Coastguard Worker
843*288bf522SAndroid Build Coastguard Worker        logging.debug('finished all disassemble jobs')
844*288bf522SAndroid Build Coastguard Worker        self.gen_addr_hit_map_in_record_info = True
845*288bf522SAndroid Build Coastguard Worker
846*288bf522SAndroid Build Coastguard Worker    def split_disassembly_jobs(self, functions: List[Function],
847*288bf522SAndroid Build Coastguard Worker                               disassemble_job_size: int) -> List[List[Function]]:
848*288bf522SAndroid Build Coastguard Worker        """ Decide how to split the task of dissassembly functions in one library. """
849*288bf522SAndroid Build Coastguard Worker        if not functions:
850*288bf522SAndroid Build Coastguard Worker            return []
851*288bf522SAndroid Build Coastguard Worker        functions.sort(key=lambda f: f.start_addr)
852*288bf522SAndroid Build Coastguard Worker        result = []
853*288bf522SAndroid Build Coastguard Worker        job_start_addr = None
854*288bf522SAndroid Build Coastguard Worker        for function in functions:
855*288bf522SAndroid Build Coastguard Worker            if (job_start_addr is None or
856*288bf522SAndroid Build Coastguard Worker                    function.start_addr - job_start_addr > disassemble_job_size):
857*288bf522SAndroid Build Coastguard Worker                job_start_addr = function.start_addr
858*288bf522SAndroid Build Coastguard Worker                result.append([function])
859*288bf522SAndroid Build Coastguard Worker            else:
860*288bf522SAndroid Build Coastguard Worker                result[-1].append(function)
861*288bf522SAndroid Build Coastguard Worker        return result
862*288bf522SAndroid Build Coastguard Worker
863*288bf522SAndroid Build Coastguard Worker    def _disassemble_functions(self, objdump: Objdump, dso_info,
864*288bf522SAndroid Build Coastguard Worker                               functions: List[Function]) -> Optional[List[Disassembly]]:
865*288bf522SAndroid Build Coastguard Worker        addr_ranges = [AddrRange(f.start_addr, f.addr_len) for f in functions]
866*288bf522SAndroid Build Coastguard Worker        return objdump.disassemble_functions(dso_info, addr_ranges)
867*288bf522SAndroid Build Coastguard Worker
868*288bf522SAndroid Build Coastguard Worker    def gen_record_info(self) -> Dict[str, Any]:
869*288bf522SAndroid Build Coastguard Worker        """ Return json data which will be used by report_html.js. """
870*288bf522SAndroid Build Coastguard Worker        record_info = {}
871*288bf522SAndroid Build Coastguard Worker        timestamp = self.meta_info.get('timestamp')
872*288bf522SAndroid Build Coastguard Worker        if timestamp:
873*288bf522SAndroid Build Coastguard Worker            t = datetime.datetime.fromtimestamp(int(timestamp))
874*288bf522SAndroid Build Coastguard Worker        else:
875*288bf522SAndroid Build Coastguard Worker            t = datetime.datetime.now()
876*288bf522SAndroid Build Coastguard Worker        record_info['recordTime'] = t.strftime('%Y-%m-%d (%A) %H:%M:%S')
877*288bf522SAndroid Build Coastguard Worker
878*288bf522SAndroid Build Coastguard Worker        product_props = self.meta_info.get('product_props')
879*288bf522SAndroid Build Coastguard Worker        machine_type = self.arch
880*288bf522SAndroid Build Coastguard Worker        if product_props:
881*288bf522SAndroid Build Coastguard Worker            manufacturer, model, name = product_props.split(':')
882*288bf522SAndroid Build Coastguard Worker            machine_type = '%s (%s) by %s, arch %s' % (model, name, manufacturer, self.arch)
883*288bf522SAndroid Build Coastguard Worker        record_info['machineType'] = machine_type
884*288bf522SAndroid Build Coastguard Worker        record_info['androidVersion'] = self.meta_info.get('android_version', '')
885*288bf522SAndroid Build Coastguard Worker        record_info['androidBuildFingerprint'] = self.meta_info.get('android_build_fingerprint', '')
886*288bf522SAndroid Build Coastguard Worker        record_info['kernelVersion'] = self.meta_info.get('kernel_version', '')
887*288bf522SAndroid Build Coastguard Worker        record_info['recordCmdline'] = self.cmdline
888*288bf522SAndroid Build Coastguard Worker        record_info['totalSamples'] = self.total_samples
889*288bf522SAndroid Build Coastguard Worker        record_info['processNames'] = self._gen_process_names()
890*288bf522SAndroid Build Coastguard Worker        record_info['threadNames'] = self._gen_thread_names()
891*288bf522SAndroid Build Coastguard Worker        record_info['libList'] = self._gen_lib_list()
892*288bf522SAndroid Build Coastguard Worker        record_info['functionMap'] = self._gen_function_map()
893*288bf522SAndroid Build Coastguard Worker        record_info['sampleInfo'] = self._gen_sample_info()
894*288bf522SAndroid Build Coastguard Worker        record_info['sourceFiles'] = self._gen_source_files()
895*288bf522SAndroid Build Coastguard Worker        return record_info
896*288bf522SAndroid Build Coastguard Worker
897*288bf522SAndroid Build Coastguard Worker    def _gen_process_names(self) -> Dict[int, str]:
898*288bf522SAndroid Build Coastguard Worker        process_names: Dict[int, str] = {}
899*288bf522SAndroid Build Coastguard Worker        for event in self.events.values():
900*288bf522SAndroid Build Coastguard Worker            for process in event.processes.values():
901*288bf522SAndroid Build Coastguard Worker                process_names[process.pid] = process.name
902*288bf522SAndroid Build Coastguard Worker        return process_names
903*288bf522SAndroid Build Coastguard Worker
904*288bf522SAndroid Build Coastguard Worker    def _gen_thread_names(self) -> Dict[int, str]:
905*288bf522SAndroid Build Coastguard Worker        thread_names: Dict[int, str] = {}
906*288bf522SAndroid Build Coastguard Worker        for event in self.events.values():
907*288bf522SAndroid Build Coastguard Worker            for process in event.processes.values():
908*288bf522SAndroid Build Coastguard Worker                for thread in process.threads.values():
909*288bf522SAndroid Build Coastguard Worker                    thread_names[thread.tid] = thread.name
910*288bf522SAndroid Build Coastguard Worker        return thread_names
911*288bf522SAndroid Build Coastguard Worker
912*288bf522SAndroid Build Coastguard Worker    def _gen_lib_list(self) -> List[str]:
913*288bf522SAndroid Build Coastguard Worker        return [modify_text_for_html(lib.name) for lib in self.libs.libs]
914*288bf522SAndroid Build Coastguard Worker
915*288bf522SAndroid Build Coastguard Worker    def _gen_function_map(self) -> Dict[int, Any]:
916*288bf522SAndroid Build Coastguard Worker        func_map: Dict[int, Any] = {}
917*288bf522SAndroid Build Coastguard Worker        for func_id in sorted(self.functions.id_to_func):
918*288bf522SAndroid Build Coastguard Worker            function = self.functions.id_to_func[func_id]
919*288bf522SAndroid Build Coastguard Worker            func_data = {}
920*288bf522SAndroid Build Coastguard Worker            func_data['l'] = function.lib_id
921*288bf522SAndroid Build Coastguard Worker            func_data['f'] = modify_text_for_html(function.func_name)
922*288bf522SAndroid Build Coastguard Worker            if function.source_info:
923*288bf522SAndroid Build Coastguard Worker                func_data['s'] = function.source_info
924*288bf522SAndroid Build Coastguard Worker            if function.disassembly:
925*288bf522SAndroid Build Coastguard Worker                disassembly_list = []
926*288bf522SAndroid Build Coastguard Worker                for code, addr in function.disassembly:
927*288bf522SAndroid Build Coastguard Worker                    disassembly_list.append(
928*288bf522SAndroid Build Coastguard Worker                        [modify_text_for_html(code),
929*288bf522SAndroid Build Coastguard Worker                         hex_address_for_json(addr)])
930*288bf522SAndroid Build Coastguard Worker                func_data['d'] = disassembly_list
931*288bf522SAndroid Build Coastguard Worker            func_map[func_id] = func_data
932*288bf522SAndroid Build Coastguard Worker        return func_map
933*288bf522SAndroid Build Coastguard Worker
934*288bf522SAndroid Build Coastguard Worker    def _gen_sample_info(self) -> List[Dict[str, Any]]:
935*288bf522SAndroid Build Coastguard Worker        return [event.get_sample_info(self.gen_addr_hit_map_in_record_info)
936*288bf522SAndroid Build Coastguard Worker                for event in self.events.values()]
937*288bf522SAndroid Build Coastguard Worker
938*288bf522SAndroid Build Coastguard Worker    def _gen_source_files(self) -> List[Dict[str, Any]]:
939*288bf522SAndroid Build Coastguard Worker        source_files = sorted(self.source_files.path_to_source_files.values(),
940*288bf522SAndroid Build Coastguard Worker                              key=lambda x: x.file_id)
941*288bf522SAndroid Build Coastguard Worker        file_list = []
942*288bf522SAndroid Build Coastguard Worker        for source_file in source_files:
943*288bf522SAndroid Build Coastguard Worker            file_data = {}
944*288bf522SAndroid Build Coastguard Worker            if not source_file.real_path:
945*288bf522SAndroid Build Coastguard Worker                file_data['path'] = ''
946*288bf522SAndroid Build Coastguard Worker                file_data['code'] = {}
947*288bf522SAndroid Build Coastguard Worker            else:
948*288bf522SAndroid Build Coastguard Worker                file_data['path'] = source_file.real_path
949*288bf522SAndroid Build Coastguard Worker                code_map = {}
950*288bf522SAndroid Build Coastguard Worker                for line in source_file.line_to_code:
951*288bf522SAndroid Build Coastguard Worker                    code_map[line] = modify_text_for_html(source_file.line_to_code[line])
952*288bf522SAndroid Build Coastguard Worker                file_data['code'] = code_map
953*288bf522SAndroid Build Coastguard Worker            file_list.append(file_data)
954*288bf522SAndroid Build Coastguard Worker        return file_list
955*288bf522SAndroid Build Coastguard Worker
956*288bf522SAndroid Build Coastguard Worker
957*288bf522SAndroid Build Coastguard WorkerURLS = {
958*288bf522SAndroid Build Coastguard Worker    'jquery': 'https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js',
959*288bf522SAndroid Build Coastguard Worker    'bootstrap4-css': 'https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/css/bootstrap.min.css',
960*288bf522SAndroid Build Coastguard Worker    'bootstrap4-popper':
961*288bf522SAndroid Build Coastguard Worker        'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js',
962*288bf522SAndroid Build Coastguard Worker    'bootstrap4': 'https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/js/bootstrap.min.js',
963*288bf522SAndroid Build Coastguard Worker    'dataTable': 'https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js',
964*288bf522SAndroid Build Coastguard Worker    'dataTable-bootstrap4': 'https://cdn.datatables.net/1.10.19/js/dataTables.bootstrap4.min.js',
965*288bf522SAndroid Build Coastguard Worker    'dataTable-css': 'https://cdn.datatables.net/1.10.19/css/dataTables.bootstrap4.min.css',
966*288bf522SAndroid Build Coastguard Worker    'gstatic-charts': 'https://www.gstatic.com/charts/loader.js',
967*288bf522SAndroid Build Coastguard Worker}
968*288bf522SAndroid Build Coastguard Worker
969*288bf522SAndroid Build Coastguard Worker
970*288bf522SAndroid Build Coastguard Workerclass ReportGenerator(object):
971*288bf522SAndroid Build Coastguard Worker
972*288bf522SAndroid Build Coastguard Worker    def __init__(self, html_path: Union[Path, str]):
973*288bf522SAndroid Build Coastguard Worker        self.hw = HtmlWriter(html_path)
974*288bf522SAndroid Build Coastguard Worker        self.hw.open_tag('html')
975*288bf522SAndroid Build Coastguard Worker        self.hw.open_tag('head')
976*288bf522SAndroid Build Coastguard Worker        for css in ['bootstrap4-css', 'dataTable-css']:
977*288bf522SAndroid Build Coastguard Worker            self.hw.open_tag('link', rel='stylesheet', type='text/css', href=URLS[css]).close_tag()
978*288bf522SAndroid Build Coastguard Worker        for js in ['jquery', 'bootstrap4-popper', 'bootstrap4', 'dataTable', 'dataTable-bootstrap4',
979*288bf522SAndroid Build Coastguard Worker                   'gstatic-charts']:
980*288bf522SAndroid Build Coastguard Worker            self.hw.open_tag('script', src=URLS[js]).close_tag()
981*288bf522SAndroid Build Coastguard Worker
982*288bf522SAndroid Build Coastguard Worker        self.hw.open_tag('script').add(
983*288bf522SAndroid Build Coastguard Worker            "google.charts.load('current', {'packages': ['corechart', 'table']});").close_tag()
984*288bf522SAndroid Build Coastguard Worker        self.hw.open_tag('style', type='text/css').add("""
985*288bf522SAndroid Build Coastguard Worker            .colForLine { width: 50px; text-align: right; }
986*288bf522SAndroid Build Coastguard Worker            .colForCount { width: 100px; text-align: right; }
987*288bf522SAndroid Build Coastguard Worker            .tableCell { font-size: 17px; }
988*288bf522SAndroid Build Coastguard Worker            .boldTableCell { font-weight: bold; font-size: 17px; }
989*288bf522SAndroid Build Coastguard Worker            .textRight { text-align: right; }
990*288bf522SAndroid Build Coastguard Worker            """).close_tag()
991*288bf522SAndroid Build Coastguard Worker        self.hw.close_tag('head')
992*288bf522SAndroid Build Coastguard Worker        self.hw.open_tag('body')
993*288bf522SAndroid Build Coastguard Worker
994*288bf522SAndroid Build Coastguard Worker    def write_content_div(self):
995*288bf522SAndroid Build Coastguard Worker        self.hw.open_tag('div', id='report_content').close_tag()
996*288bf522SAndroid Build Coastguard Worker
997*288bf522SAndroid Build Coastguard Worker    def write_record_data(self, record_data: Dict[str, Any]):
998*288bf522SAndroid Build Coastguard Worker        self.hw.open_tag('script', id='record_data', type='application/json')
999*288bf522SAndroid Build Coastguard Worker        self.hw.add(json.dumps(record_data))
1000*288bf522SAndroid Build Coastguard Worker        self.hw.close_tag()
1001*288bf522SAndroid Build Coastguard Worker
1002*288bf522SAndroid Build Coastguard Worker    def write_script(self):
1003*288bf522SAndroid Build Coastguard Worker        self.hw.open_tag('script').add_file('report_html.js').close_tag()
1004*288bf522SAndroid Build Coastguard Worker
1005*288bf522SAndroid Build Coastguard Worker    def finish(self):
1006*288bf522SAndroid Build Coastguard Worker        self.hw.close_tag('body')
1007*288bf522SAndroid Build Coastguard Worker        self.hw.close_tag('html')
1008*288bf522SAndroid Build Coastguard Worker        self.hw.close()
1009*288bf522SAndroid Build Coastguard Worker
1010*288bf522SAndroid Build Coastguard Worker
1011*288bf522SAndroid Build Coastguard Workerdef get_args() -> argparse.Namespace:
1012*288bf522SAndroid Build Coastguard Worker    parser = BaseArgumentParser(description='report profiling data')
1013*288bf522SAndroid Build Coastguard Worker    parser.add_argument('-i', '--record_file', nargs='+', default=['perf.data'], help="""
1014*288bf522SAndroid Build Coastguard Worker                        Set profiling data file to report.""")
1015*288bf522SAndroid Build Coastguard Worker    parser.add_argument('-o', '--report_path', default='report.html', help='Set output html file')
1016*288bf522SAndroid Build Coastguard Worker    parser.add_argument('--min_func_percent', default=0.01, type=float, help="""
1017*288bf522SAndroid Build Coastguard Worker                        Set min percentage of functions shown in the report.
1018*288bf522SAndroid Build Coastguard Worker                        For example, when set to 0.01, only functions taking >= 0.01%% of total
1019*288bf522SAndroid Build Coastguard Worker                        event count are collected in the report.""")
1020*288bf522SAndroid Build Coastguard Worker    parser.add_argument('--min_callchain_percent', default=0.01, type=float, help="""
1021*288bf522SAndroid Build Coastguard Worker                        Set min percentage of callchains shown in the report.
1022*288bf522SAndroid Build Coastguard Worker                        It is used to limit nodes shown in the function flamegraph. For example,
1023*288bf522SAndroid Build Coastguard Worker                        when set to 0.01, only callchains taking >= 0.01%% of the event count of
1024*288bf522SAndroid Build Coastguard Worker                        the starting function are collected in the report.""")
1025*288bf522SAndroid Build Coastguard Worker    parser.add_argument('--add_source_code', action='store_true', help='Add source code.')
1026*288bf522SAndroid Build Coastguard Worker    parser.add_argument('--source_dirs', nargs='+', help='Source code directories.')
1027*288bf522SAndroid Build Coastguard Worker    parser.add_argument('--add_disassembly', action='store_true', help='Add disassembled code.')
1028*288bf522SAndroid Build Coastguard Worker    parser.add_argument('--disassemble-job-size', type=int, default=1024*1024,
1029*288bf522SAndroid Build Coastguard Worker                        help='address range for one disassemble job')
1030*288bf522SAndroid Build Coastguard Worker    parser.add_argument('--binary_filter', nargs='+', help="""Annotate source code and disassembly
1031*288bf522SAndroid Build Coastguard Worker                        only for selected binaries, whose recorded paths contains [BINARY_FILTER] as
1032*288bf522SAndroid Build Coastguard Worker                        a substring. Example: to select binaries belonging to an app with package
1033*288bf522SAndroid Build Coastguard Worker                        name 'com.example.myapp', use `--binary_filter com.example.myapp`.
1034*288bf522SAndroid Build Coastguard Worker                        """)
1035*288bf522SAndroid Build Coastguard Worker    parser.add_argument(
1036*288bf522SAndroid Build Coastguard Worker        '-j', '--jobs', type=int, default=os.cpu_count(),
1037*288bf522SAndroid Build Coastguard Worker        help='Use multithreading to speed up disassembly and source code annotation.')
1038*288bf522SAndroid Build Coastguard Worker    parser.add_argument('--ndk_path', nargs=1, help='Find tools in the ndk path.')
1039*288bf522SAndroid Build Coastguard Worker    parser.add_argument('--no_browser', action='store_true', help="Don't open report in browser.")
1040*288bf522SAndroid Build Coastguard Worker    parser.add_argument('--aggregate-by-thread-name', action='store_true', help="""aggregate
1041*288bf522SAndroid Build Coastguard Worker                        samples by thread name instead of thread id. This is useful for
1042*288bf522SAndroid Build Coastguard Worker                        showing multiple perf.data generated for the same app.""")
1043*288bf522SAndroid Build Coastguard Worker    parser.add_report_lib_options()
1044*288bf522SAndroid Build Coastguard Worker    return parser.parse_args()
1045*288bf522SAndroid Build Coastguard Worker
1046*288bf522SAndroid Build Coastguard Worker
1047*288bf522SAndroid Build Coastguard Workerdef main():
1048*288bf522SAndroid Build Coastguard Worker    sys.setrecursionlimit(MAX_CALLSTACK_LENGTH * 2 + 50)
1049*288bf522SAndroid Build Coastguard Worker    args = get_args()
1050*288bf522SAndroid Build Coastguard Worker
1051*288bf522SAndroid Build Coastguard Worker    # 1. Process args.
1052*288bf522SAndroid Build Coastguard Worker    binary_cache_path = 'binary_cache'
1053*288bf522SAndroid Build Coastguard Worker    if not os.path.isdir(binary_cache_path):
1054*288bf522SAndroid Build Coastguard Worker        if args.add_source_code or args.add_disassembly:
1055*288bf522SAndroid Build Coastguard Worker            log_exit("""binary_cache/ doesn't exist. Can't add source code or disassembled code
1056*288bf522SAndroid Build Coastguard Worker                        without collected binaries. Please run binary_cache_builder.py to
1057*288bf522SAndroid Build Coastguard Worker                        collect binaries for current profiling data, or run app_profiler.py
1058*288bf522SAndroid Build Coastguard Worker                        without -nb option.""")
1059*288bf522SAndroid Build Coastguard Worker        binary_cache_path = None
1060*288bf522SAndroid Build Coastguard Worker
1061*288bf522SAndroid Build Coastguard Worker    if args.add_source_code and not args.source_dirs:
1062*288bf522SAndroid Build Coastguard Worker        log_exit('--source_dirs is needed to add source code.')
1063*288bf522SAndroid Build Coastguard Worker    build_addr_hit_map = args.add_source_code or args.add_disassembly
1064*288bf522SAndroid Build Coastguard Worker    ndk_path = None if not args.ndk_path else args.ndk_path[0]
1065*288bf522SAndroid Build Coastguard Worker    if args.jobs < 1:
1066*288bf522SAndroid Build Coastguard Worker        log_exit('Invalid --jobs option.')
1067*288bf522SAndroid Build Coastguard Worker
1068*288bf522SAndroid Build Coastguard Worker    # 2. Produce record data.
1069*288bf522SAndroid Build Coastguard Worker    record_data = RecordData(binary_cache_path, ndk_path, build_addr_hit_map)
1070*288bf522SAndroid Build Coastguard Worker    for record_file in args.record_file:
1071*288bf522SAndroid Build Coastguard Worker        record_data.load_record_file(record_file, args.report_lib_options)
1072*288bf522SAndroid Build Coastguard Worker    if args.aggregate_by_thread_name:
1073*288bf522SAndroid Build Coastguard Worker        record_data.aggregate_by_thread_name()
1074*288bf522SAndroid Build Coastguard Worker    record_data.limit_percents(args.min_func_percent, args.min_callchain_percent)
1075*288bf522SAndroid Build Coastguard Worker    record_data.sort_call_graph_by_function_name()
1076*288bf522SAndroid Build Coastguard Worker
1077*288bf522SAndroid Build Coastguard Worker    def filter_lib(lib_name: str) -> bool:
1078*288bf522SAndroid Build Coastguard Worker        if not args.binary_filter:
1079*288bf522SAndroid Build Coastguard Worker            return True
1080*288bf522SAndroid Build Coastguard Worker        for binary in args.binary_filter:
1081*288bf522SAndroid Build Coastguard Worker            if binary in lib_name:
1082*288bf522SAndroid Build Coastguard Worker                return True
1083*288bf522SAndroid Build Coastguard Worker        return False
1084*288bf522SAndroid Build Coastguard Worker    if args.add_source_code:
1085*288bf522SAndroid Build Coastguard Worker        record_data.add_source_code(args.source_dirs, filter_lib, args.jobs)
1086*288bf522SAndroid Build Coastguard Worker    if args.add_disassembly:
1087*288bf522SAndroid Build Coastguard Worker        record_data.add_disassembly(filter_lib, args.jobs, args.disassemble_job_size)
1088*288bf522SAndroid Build Coastguard Worker
1089*288bf522SAndroid Build Coastguard Worker    # 3. Generate report html.
1090*288bf522SAndroid Build Coastguard Worker    report_generator = ReportGenerator(args.report_path)
1091*288bf522SAndroid Build Coastguard Worker    report_generator.write_script()
1092*288bf522SAndroid Build Coastguard Worker    report_generator.write_content_div()
1093*288bf522SAndroid Build Coastguard Worker    report_generator.write_record_data(record_data.gen_record_info())
1094*288bf522SAndroid Build Coastguard Worker    report_generator.finish()
1095*288bf522SAndroid Build Coastguard Worker
1096*288bf522SAndroid Build Coastguard Worker    if not args.no_browser:
1097*288bf522SAndroid Build Coastguard Worker        open_report_in_browser(args.report_path)
1098*288bf522SAndroid Build Coastguard Worker    logging.info("Report generated at '%s'." % args.report_path)
1099*288bf522SAndroid Build Coastguard Worker
1100*288bf522SAndroid Build Coastguard Worker
1101*288bf522SAndroid Build Coastguard Workerif __name__ == '__main__':
1102*288bf522SAndroid Build Coastguard Worker    main()
1103