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('>', '>').replace('<', '<') 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