1e9e5710d43556989f48525202460971de3da772aAndreas Gampe#!/usr/bin/python 2e9e5710d43556989f48525202460971de3da772aAndreas Gampe# 3e9e5710d43556989f48525202460971de3da772aAndreas Gampe# Copyright (C) 2018 The Android Open Source Project 4e9e5710d43556989f48525202460971de3da772aAndreas Gampe# 5e9e5710d43556989f48525202460971de3da772aAndreas Gampe# Licensed under the Apache License, Version 2.0 (the "License"); 6e9e5710d43556989f48525202460971de3da772aAndreas Gampe# you may not use this file except in compliance with the License. 7e9e5710d43556989f48525202460971de3da772aAndreas Gampe# You may obtain a copy of the License at 8e9e5710d43556989f48525202460971de3da772aAndreas Gampe# 9e9e5710d43556989f48525202460971de3da772aAndreas Gampe# http://www.apache.org/licenses/LICENSE-2.0 10e9e5710d43556989f48525202460971de3da772aAndreas Gampe# 11e9e5710d43556989f48525202460971de3da772aAndreas Gampe# Unless required by applicable law or agreed to in writing, software 12e9e5710d43556989f48525202460971de3da772aAndreas Gampe# distributed under the License is distributed on an "AS IS" BASIS, 13e9e5710d43556989f48525202460971de3da772aAndreas Gampe# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14e9e5710d43556989f48525202460971de3da772aAndreas Gampe# See the License for the specific language governing permissions and 15e9e5710d43556989f48525202460971de3da772aAndreas Gampe# limitations under the License. 16e9e5710d43556989f48525202460971de3da772aAndreas Gampe 17e9e5710d43556989f48525202460971de3da772aAndreas Gampe# Make sure that simpleperf's inferno is on the PYTHONPATH, e.g., run as 18e9e5710d43556989f48525202460971de3da772aAndreas Gampe# PYTHONPATH=$PYTHONPATH:$ANDROID_BUILD_TOP/system/extras/simpleperf/scripts/inferno python .. 19e9e5710d43556989f48525202460971de3da772aAndreas Gampe 20e9e5710d43556989f48525202460971de3da772aAndreas Gampeimport argparse 21e9e5710d43556989f48525202460971de3da772aAndreas Gampeimport itertools 22e9e5710d43556989f48525202460971de3da772aAndreas Gampeimport sqlite3 23e9e5710d43556989f48525202460971de3da772aAndreas Gampe 24e9e5710d43556989f48525202460971de3da772aAndreas Gampeclass Callsite(object): 25e9e5710d43556989f48525202460971de3da772aAndreas Gampe def __init__(self, dso_id, sym_id): 26e9e5710d43556989f48525202460971de3da772aAndreas Gampe self.dso_id = dso_id 27e9e5710d43556989f48525202460971de3da772aAndreas Gampe self.sym_id = sym_id 28e9e5710d43556989f48525202460971de3da772aAndreas Gampe self.count = 0 29e9e5710d43556989f48525202460971de3da772aAndreas Gampe self.child_map = {} 30e9e5710d43556989f48525202460971de3da772aAndreas Gampe self.id = self._get_next_callsite_id() 31e9e5710d43556989f48525202460971de3da772aAndreas Gampe 32e9e5710d43556989f48525202460971de3da772aAndreas Gampe def add(self, dso_id, sym_id): 33e9e5710d43556989f48525202460971de3da772aAndreas Gampe if (dso_id, sym_id) in self.child_map: 34e9e5710d43556989f48525202460971de3da772aAndreas Gampe return self.child_map[(dso_id, sym_id)] 35e9e5710d43556989f48525202460971de3da772aAndreas Gampe new_callsite = Callsite(dso_id, sym_id) 36e9e5710d43556989f48525202460971de3da772aAndreas Gampe self.child_map[(dso_id, sym_id)] = new_callsite 37e9e5710d43556989f48525202460971de3da772aAndreas Gampe return new_callsite 38e9e5710d43556989f48525202460971de3da772aAndreas Gampe 39e9e5710d43556989f48525202460971de3da772aAndreas Gampe def child_count_to_self(self): 40e9e5710d43556989f48525202460971de3da772aAndreas Gampe self.count = reduce(lambda x, y: x + y[1].count, self.child_map.iteritems(), 0) 41e9e5710d43556989f48525202460971de3da772aAndreas Gampe 42e9e5710d43556989f48525202460971de3da772aAndreas Gampe def trim(self, local_threshold_in_percent, global_threshold): 43e9e5710d43556989f48525202460971de3da772aAndreas Gampe local_threshold = local_threshold_in_percent * 0.01 * self.count 44e9e5710d43556989f48525202460971de3da772aAndreas Gampe threshold = max(local_threshold, global_threshold) 45e9e5710d43556989f48525202460971de3da772aAndreas Gampe for k, v in self.child_map.items(): 46e9e5710d43556989f48525202460971de3da772aAndreas Gampe if v.count < threshold: 47e9e5710d43556989f48525202460971de3da772aAndreas Gampe del self.child_map[k] 48e9e5710d43556989f48525202460971de3da772aAndreas Gampe for _, v in self.child_map.iteritems(): 49e9e5710d43556989f48525202460971de3da772aAndreas Gampe v.trim(local_threshold_in_percent, global_threshold) 50e9e5710d43556989f48525202460971de3da772aAndreas Gampe 51e9e5710d43556989f48525202460971de3da772aAndreas Gampe def _get_str(self, id, m): 52e9e5710d43556989f48525202460971de3da772aAndreas Gampe if id in m: 53e9e5710d43556989f48525202460971de3da772aAndreas Gampe return m[id] 54e9e5710d43556989f48525202460971de3da772aAndreas Gampe return str(id) 55e9e5710d43556989f48525202460971de3da772aAndreas Gampe 56e9e5710d43556989f48525202460971de3da772aAndreas Gampe def print_callsite_ascii(self, depth, indent, dsos, syms): 57e9e5710d43556989f48525202460971de3da772aAndreas Gampe 58e9e5710d43556989f48525202460971de3da772aAndreas Gampe print ' ' * indent + "%s (%s) [%d]" % (self._get_str(self.sym_id, syms), 59e9e5710d43556989f48525202460971de3da772aAndreas Gampe self._get_str(self.dso_id, dsos), 60e9e5710d43556989f48525202460971de3da772aAndreas Gampe self.count) 61e9e5710d43556989f48525202460971de3da772aAndreas Gampe if depth == 0: 62e9e5710d43556989f48525202460971de3da772aAndreas Gampe return 63e9e5710d43556989f48525202460971de3da772aAndreas Gampe for v in sorted(self.child_map.itervalues, key=lambda x: x.count, reverse=True): 64e9e5710d43556989f48525202460971de3da772aAndreas Gampe v.print_callsite_ascii(depth - 1, indent + 1, dsos, syms) 65e9e5710d43556989f48525202460971de3da772aAndreas Gampe 66e9e5710d43556989f48525202460971de3da772aAndreas Gampe # Functions for flamegraph compatibility. 67e9e5710d43556989f48525202460971de3da772aAndreas Gampe 68e9e5710d43556989f48525202460971de3da772aAndreas Gampe callsite_counter = 0 69e9e5710d43556989f48525202460971de3da772aAndreas Gampe @classmethod 70e9e5710d43556989f48525202460971de3da772aAndreas Gampe def _get_next_callsite_id(cls): 71e9e5710d43556989f48525202460971de3da772aAndreas Gampe cls.callsite_counter += 1 72e9e5710d43556989f48525202460971de3da772aAndreas Gampe return cls.callsite_counter 73e9e5710d43556989f48525202460971de3da772aAndreas Gampe 74e9e5710d43556989f48525202460971de3da772aAndreas Gampe def create_children_list(self): 75e9e5710d43556989f48525202460971de3da772aAndreas Gampe self.children = sorted(self.child_map.itervalues(), key=lambda x: x.count, reverse=True) 76e9e5710d43556989f48525202460971de3da772aAndreas Gampe 77e9e5710d43556989f48525202460971de3da772aAndreas Gampe def generate_offset(self, start_offset): 78e9e5710d43556989f48525202460971de3da772aAndreas Gampe self.offset = start_offset 79e9e5710d43556989f48525202460971de3da772aAndreas Gampe child_offset = start_offset 80e9e5710d43556989f48525202460971de3da772aAndreas Gampe for child in self.children: 81e9e5710d43556989f48525202460971de3da772aAndreas Gampe child_offset = child.generate_offset(child_offset) 82e9e5710d43556989f48525202460971de3da772aAndreas Gampe return self.offset + self.count 83e9e5710d43556989f48525202460971de3da772aAndreas Gampe 84e9e5710d43556989f48525202460971de3da772aAndreas Gampe def svgrenderer_compat(self, dsos, syms): 85e9e5710d43556989f48525202460971de3da772aAndreas Gampe self.create_children_list() 86e9e5710d43556989f48525202460971de3da772aAndreas Gampe self.method = self._get_str(self.sym_id, syms) 87e9e5710d43556989f48525202460971de3da772aAndreas Gampe self.dso = self._get_str(self.dso_id, dsos) 88e9e5710d43556989f48525202460971de3da772aAndreas Gampe self.offset = 0 89e9e5710d43556989f48525202460971de3da772aAndreas Gampe for c in self.children: 90e9e5710d43556989f48525202460971de3da772aAndreas Gampe c.svgrenderer_compat(dsos, syms) 91e9e5710d43556989f48525202460971de3da772aAndreas Gampe 92e9e5710d43556989f48525202460971de3da772aAndreas Gampe def weight(self): 93e9e5710d43556989f48525202460971de3da772aAndreas Gampe return float(self.count) 94e9e5710d43556989f48525202460971de3da772aAndreas Gampe 95e9e5710d43556989f48525202460971de3da772aAndreas Gampe def get_max_depth(self): 96e9e5710d43556989f48525202460971de3da772aAndreas Gampe if self.child_map: 97e9e5710d43556989f48525202460971de3da772aAndreas Gampe return max([c.get_max_depth() for c in self.child_map.itervalues()]) + 1 98e9e5710d43556989f48525202460971de3da772aAndreas Gampe return 1 99e9e5710d43556989f48525202460971de3da772aAndreas Gampe 100e9e5710d43556989f48525202460971de3da772aAndreas Gampeclass SqliteReader(object): 101e9e5710d43556989f48525202460971de3da772aAndreas Gampe def __init__(self): 102e9e5710d43556989f48525202460971de3da772aAndreas Gampe self.root = Callsite("root", "root") 103e9e5710d43556989f48525202460971de3da772aAndreas Gampe self.dsos = {} 104e9e5710d43556989f48525202460971de3da772aAndreas Gampe self.syms = {} 105e9e5710d43556989f48525202460971de3da772aAndreas Gampe 106e9e5710d43556989f48525202460971de3da772aAndreas Gampe def open(self, f): 107e9e5710d43556989f48525202460971de3da772aAndreas Gampe self._conn = sqlite3.connect(f) 108e9e5710d43556989f48525202460971de3da772aAndreas Gampe self._c = self._conn.cursor() 109e9e5710d43556989f48525202460971de3da772aAndreas Gampe 110e9e5710d43556989f48525202460971de3da772aAndreas Gampe def close(self): 111e9e5710d43556989f48525202460971de3da772aAndreas Gampe self._conn.close() 112e9e5710d43556989f48525202460971de3da772aAndreas Gampe 113e9e5710d43556989f48525202460971de3da772aAndreas Gampe def read(self, local_threshold_in_percent, global_threshold_in_percent, limit): 114e9e5710d43556989f48525202460971de3da772aAndreas Gampe # Read aux tables first, as we need to find the kernel symbols. 115e9e5710d43556989f48525202460971de3da772aAndreas Gampe def read_table(name, dest_table): 116e9e5710d43556989f48525202460971de3da772aAndreas Gampe self._c.execute('select id, name from %s' % (name)) 117e9e5710d43556989f48525202460971de3da772aAndreas Gampe while True: 118e9e5710d43556989f48525202460971de3da772aAndreas Gampe rows = self._c.fetchmany(100) 119e9e5710d43556989f48525202460971de3da772aAndreas Gampe if not rows: 120e9e5710d43556989f48525202460971de3da772aAndreas Gampe break 121e9e5710d43556989f48525202460971de3da772aAndreas Gampe for row in rows: 122e9e5710d43556989f48525202460971de3da772aAndreas Gampe dest_table[row[0]] = row[1] 123e9e5710d43556989f48525202460971de3da772aAndreas Gampe 124e9e5710d43556989f48525202460971de3da772aAndreas Gampe print 'Reading DSOs' 125e9e5710d43556989f48525202460971de3da772aAndreas Gampe read_table('dsos', self.dsos) 126e9e5710d43556989f48525202460971de3da772aAndreas Gampe 127e9e5710d43556989f48525202460971de3da772aAndreas Gampe print 'Reading symbol strings' 128e9e5710d43556989f48525202460971de3da772aAndreas Gampe read_table('syms', self.syms) 129e9e5710d43556989f48525202460971de3da772aAndreas Gampe 130e9e5710d43556989f48525202460971de3da772aAndreas Gampe kernel_sym_id = None 131e9e5710d43556989f48525202460971de3da772aAndreas Gampe for i, v in self.syms.iteritems(): 132e9e5710d43556989f48525202460971de3da772aAndreas Gampe if v == '[kernel]': 133e9e5710d43556989f48525202460971de3da772aAndreas Gampe kernel_sym_id = i 134e9e5710d43556989f48525202460971de3da772aAndreas Gampe break 135e9e5710d43556989f48525202460971de3da772aAndreas Gampe 136e9e5710d43556989f48525202460971de3da772aAndreas Gampe print 'Reading samples' 137e9e5710d43556989f48525202460971de3da772aAndreas Gampe self._c.execute('''select sample_id, depth, dso_id, sym_id from stacks 138e9e5710d43556989f48525202460971de3da772aAndreas Gampe order by sample_id asc, depth desc''') 139e9e5710d43556989f48525202460971de3da772aAndreas Gampe 140e9e5710d43556989f48525202460971de3da772aAndreas Gampe last_sample_id = None 141e9e5710d43556989f48525202460971de3da772aAndreas Gampe chain = None 142e9e5710d43556989f48525202460971de3da772aAndreas Gampe count = 0 143e9e5710d43556989f48525202460971de3da772aAndreas Gampe while True: 144e9e5710d43556989f48525202460971de3da772aAndreas Gampe rows = self._c.fetchmany(100) 145e9e5710d43556989f48525202460971de3da772aAndreas Gampe 146e9e5710d43556989f48525202460971de3da772aAndreas Gampe if not rows: 147e9e5710d43556989f48525202460971de3da772aAndreas Gampe break 148e9e5710d43556989f48525202460971de3da772aAndreas Gampe for row in rows: 149e9e5710d43556989f48525202460971de3da772aAndreas Gampe if row[3] == kernel_sym_id and row[1] == 0: 150e9e5710d43556989f48525202460971de3da772aAndreas Gampe # Skip kernel. 151e9e5710d43556989f48525202460971de3da772aAndreas Gampe continue 152e9e5710d43556989f48525202460971de3da772aAndreas Gampe if row[0] != last_sample_id: 153e9e5710d43556989f48525202460971de3da772aAndreas Gampe last_sample_id = row[0] 154e9e5710d43556989f48525202460971de3da772aAndreas Gampe chain = self.root 155e9e5710d43556989f48525202460971de3da772aAndreas Gampe chain = chain.add(row[2], row[3]) 156e9e5710d43556989f48525202460971de3da772aAndreas Gampe chain.count = chain.count + 1 157e9e5710d43556989f48525202460971de3da772aAndreas Gampe 158e9e5710d43556989f48525202460971de3da772aAndreas Gampe count = count + len(rows) 159e9e5710d43556989f48525202460971de3da772aAndreas Gampe if limit is not None and count >= limit: 160e9e5710d43556989f48525202460971de3da772aAndreas Gampe print 'Breaking as limit is reached' 161e9e5710d43556989f48525202460971de3da772aAndreas Gampe break 162e9e5710d43556989f48525202460971de3da772aAndreas Gampe 163e9e5710d43556989f48525202460971de3da772aAndreas Gampe self.root.child_count_to_self() 164e9e5710d43556989f48525202460971de3da772aAndreas Gampe global_threshold = global_threshold_in_percent * 0.01 * self.root.count 165e9e5710d43556989f48525202460971de3da772aAndreas Gampe self.root.trim(local_threshold_in_percent, global_threshold) 166e9e5710d43556989f48525202460971de3da772aAndreas Gampe 167e9e5710d43556989f48525202460971de3da772aAndreas Gampe def print_data_ascii(self, depth): 168e9e5710d43556989f48525202460971de3da772aAndreas Gampe self.root.print_callsite_ascii(depth, 0, self.dsos, self.syms) 169e9e5710d43556989f48525202460971de3da772aAndreas Gampe 170e9e5710d43556989f48525202460971de3da772aAndreas Gampe def print_svg(self, filename, depth): 171e9e5710d43556989f48525202460971de3da772aAndreas Gampe from svg_renderer import renderSVG 172e9e5710d43556989f48525202460971de3da772aAndreas Gampe self.root.svgrenderer_compat(self.dsos, self.syms) 173e9e5710d43556989f48525202460971de3da772aAndreas Gampe self.root.generate_offset(0) 174e9e5710d43556989f48525202460971de3da772aAndreas Gampe f = open(filename, 'w') 175e9e5710d43556989f48525202460971de3da772aAndreas Gampe f.write(''' 176e9e5710d43556989f48525202460971de3da772aAndreas Gampe<html> 177e9e5710d43556989f48525202460971de3da772aAndreas Gampe<body> 178e9e5710d43556989f48525202460971de3da772aAndreas Gampe<div id='flamegraph_id' style='font-family: Monospace;'> 179e9e5710d43556989f48525202460971de3da772aAndreas Gampe<style type="text/css"> .s { stroke:black; stroke-width:0.5; cursor:pointer;} </style> 180e9e5710d43556989f48525202460971de3da772aAndreas Gampe<style type="text/css"> .t:hover { cursor:pointer; } </style> 181e9e5710d43556989f48525202460971de3da772aAndreas Gampe''') 182e9e5710d43556989f48525202460971de3da772aAndreas Gampe 183e9e5710d43556989f48525202460971de3da772aAndreas Gampe class FakeProcess: 184e9e5710d43556989f48525202460971de3da772aAndreas Gampe def __init__(self): 185e9e5710d43556989f48525202460971de3da772aAndreas Gampe self.props = { 'trace_offcpu': False } 186e9e5710d43556989f48525202460971de3da772aAndreas Gampe fake_process = FakeProcess() 187e9e5710d43556989f48525202460971de3da772aAndreas Gampe renderSVG(fake_process, self.root, f, 'hot') 188e9e5710d43556989f48525202460971de3da772aAndreas Gampe 189e9e5710d43556989f48525202460971de3da772aAndreas Gampe f.write(''' 190e9e5710d43556989f48525202460971de3da772aAndreas Gampe</div> 191e9e5710d43556989f48525202460971de3da772aAndreas Gampe''') 192e9e5710d43556989f48525202460971de3da772aAndreas Gampe 193e9e5710d43556989f48525202460971de3da772aAndreas Gampe # Emit script.js, if we can find it. 194e9e5710d43556989f48525202460971de3da772aAndreas Gampe import os.path 195e9e5710d43556989f48525202460971de3da772aAndreas Gampe import sys 196e9e5710d43556989f48525202460971de3da772aAndreas Gampe script_js_rel = "../../simpleperf/scripts/inferno/script.js" 197e9e5710d43556989f48525202460971de3da772aAndreas Gampe script_js = os.path.join(os.path.dirname(__file__), script_js_rel) 198e9e5710d43556989f48525202460971de3da772aAndreas Gampe if os.path.exists(script_js): 199e9e5710d43556989f48525202460971de3da772aAndreas Gampe f.write('<script>\n') 200e9e5710d43556989f48525202460971de3da772aAndreas Gampe with open(script_js, 'r') as script_f: 201e9e5710d43556989f48525202460971de3da772aAndreas Gampe f.write(script_f.read()) 202e9e5710d43556989f48525202460971de3da772aAndreas Gampe f.write(''' 203e9e5710d43556989f48525202460971de3da772aAndreas Gampe</script> 204e9e5710d43556989f48525202460971de3da772aAndreas Gampe<br/><br/> 205e9e5710d43556989f48525202460971de3da772aAndreas Gampe<div>Navigate with WASD, zoom in with SPACE, zoom out with BACKSPACE.</div> 206e9e5710d43556989f48525202460971de3da772aAndreas Gampe<script>document.addEventListener('DOMContentLoaded', flamegraphInit);</script> 207e9e5710d43556989f48525202460971de3da772aAndreas Gampe</body> 208e9e5710d43556989f48525202460971de3da772aAndreas Gampe</html> 209e9e5710d43556989f48525202460971de3da772aAndreas Gampe''') 210e9e5710d43556989f48525202460971de3da772aAndreas Gampe f.close() 211e9e5710d43556989f48525202460971de3da772aAndreas Gampe 212e9e5710d43556989f48525202460971de3da772aAndreas Gampeif __name__ == "__main__": 213e9e5710d43556989f48525202460971de3da772aAndreas Gampe parser = argparse.ArgumentParser(description='''Translate a perfprofd database into a flame 214e9e5710d43556989f48525202460971de3da772aAndreas Gampe representation''') 215e9e5710d43556989f48525202460971de3da772aAndreas Gampe 216e9e5710d43556989f48525202460971de3da772aAndreas Gampe parser.add_argument('file', help='the sqlite database to use', metavar='file', type=str) 217e9e5710d43556989f48525202460971de3da772aAndreas Gampe 218e9e5710d43556989f48525202460971de3da772aAndreas Gampe parser.add_argument('--html-out', help='output file for HTML flame graph', type=str) 219e9e5710d43556989f48525202460971de3da772aAndreas Gampe parser.add_argument('--threshold', help='child threshold in percent', type=float, default=5) 220e9e5710d43556989f48525202460971de3da772aAndreas Gampe parser.add_argument('--global-threshold', help='global threshold in percent', type=float, 221e9e5710d43556989f48525202460971de3da772aAndreas Gampe default=.1) 222e9e5710d43556989f48525202460971de3da772aAndreas Gampe parser.add_argument('--depth', help='depth to print to', type=int, default=10) 223e9e5710d43556989f48525202460971de3da772aAndreas Gampe parser.add_argument('--limit', help='limit to given number of stack trace entries', type=int) 224e9e5710d43556989f48525202460971de3da772aAndreas Gampe 225e9e5710d43556989f48525202460971de3da772aAndreas Gampe args = parser.parse_args() 226e9e5710d43556989f48525202460971de3da772aAndreas Gampe if args is not None: 227e9e5710d43556989f48525202460971de3da772aAndreas Gampe sql_out = SqliteReader() 228e9e5710d43556989f48525202460971de3da772aAndreas Gampe sql_out.open(args.file) 229e9e5710d43556989f48525202460971de3da772aAndreas Gampe sql_out.read(args.threshold, args.global_threshold, args.limit) 230e9e5710d43556989f48525202460971de3da772aAndreas Gampe if args.html_out is None: 231e9e5710d43556989f48525202460971de3da772aAndreas Gampe sql_out.print_data_ascii(args.depth) 232e9e5710d43556989f48525202460971de3da772aAndreas Gampe else: 233e9e5710d43556989f48525202460971de3da772aAndreas Gampe sql_out.print_svg(args.html_out, args.depth) 234e9e5710d43556989f48525202460971de3da772aAndreas Gampe sql_out.close() 235