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