OSDN Git Service

extras: remove su, we have our own
[android-x86/system-extras.git] / simpleperf / report.py
1 #!/usr/bin/env python
2 #
3 # Copyright (C) 2015 The Android Open Source Project
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 #      http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 #
17
18 """Simpleperf gui reporter: provide gui interface for simpleperf report command.
19
20 There are two ways to use gui reporter. One way is to pass it a report file
21 generated by simpleperf report command, and reporter will display it. The
22 other ways is to pass it any arguments you want to use when calling
23 simpleperf report command. The reporter will call `simpleperf report` to
24 generate report file, and display it.
25 """
26
27 import os.path
28 import re
29 import subprocess
30 import sys
31 from tkFont import *
32 from Tkinter import *
33 from ttk import *
34
35 PAD_X = 3
36 PAD_Y = 3
37
38
39 class CallTreeNode(object):
40
41   """Representing a node in call-graph."""
42
43   def __init__(self, percentage, function_name):
44     self.percentage = percentage
45     self.call_stack = [function_name]
46     self.children = []
47
48   def add_call(self, function_name):
49     self.call_stack.append(function_name)
50
51   def add_child(self, node):
52     self.children.append(node)
53
54   def __str__(self):
55     strs = self.dump()
56     return '\n'.join(strs)
57
58   def dump(self):
59     strs = []
60     strs.append('CallTreeNode percentage = %.2f' % self.percentage)
61     for function_name in self.call_stack:
62       strs.append(' %s' % function_name)
63     for child in self.children:
64       child_strs = child.dump()
65       strs.extend(['  ' + x for x in child_strs])
66     return strs
67
68
69 class ReportItem(object):
70
71   """Representing one item in report, may contain a CallTree."""
72
73   def __init__(self, raw_line):
74     self.raw_line = raw_line
75     self.call_tree = None
76
77   def __str__(self):
78     strs = []
79     strs.append('ReportItem (raw_line %s)' % self.raw_line)
80     if self.call_tree is not None:
81       strs.append('%s' % self.call_tree)
82     return '\n'.join(strs)
83
84
85 def parse_report_items(lines):
86   report_items = []
87   cur_report_item = None
88   call_tree_stack = {}
89   vertical_columns = []
90   last_node = None
91
92   for line in lines:
93     if not line:
94       continue
95     if not line[0].isspace():
96       cur_report_item = ReportItem(line)
97       report_items.append(cur_report_item)
98       # Each report item can have different column depths.
99       vertical_columns = []
100     else:
101       for i in range(len(line)):
102         if line[i] == '|':
103           if not vertical_columns or vertical_columns[-1] < i:
104             vertical_columns.append(i)
105
106       if not line.strip('| \t'):
107         continue
108       if line.find('-') == -1:
109         line = line.strip('| \t')
110         function_name = line
111         last_node.add_call(function_name)
112       else:
113         pos = line.find('-')
114         depth = -1
115         for i in range(len(vertical_columns)):
116           if pos >= vertical_columns[i]:
117             depth = i
118         assert depth != -1
119
120         line = line.strip('|- \t')
121         m = re.search(r'^([\d\.]+)%[-\s]+(.+)$', line)
122         if m:
123           percentage = float(m.group(1))
124           function_name = m.group(2)
125         else:
126           percentage = 100.0
127           function_name = line
128
129         node = CallTreeNode(percentage, function_name)
130         if depth == 0:
131           cur_report_item.call_tree = node
132         else:
133           call_tree_stack[depth - 1].add_child(node)
134         call_tree_stack[depth] = node
135         last_node = node
136
137   return report_items
138
139
140 class ReportWindow(object):
141
142   """A window used to display report file."""
143
144   def __init__(self, master, report_context, title_line, report_items):
145     frame = Frame(master)
146     frame.pack(fill=BOTH, expand=1)
147
148     font = Font(family='courier', size=10)
149
150     # Report Context
151     for line in report_context:
152       label = Label(frame, text=line, font=font)
153       label.pack(anchor=W, padx=PAD_X, pady=PAD_Y)
154
155     # Space
156     label = Label(frame, text='', font=font)
157     label.pack(anchor=W, padx=PAD_X, pady=PAD_Y)
158
159     # Title
160     label = Label(frame, text='  ' + title_line, font=font)
161     label.pack(anchor=W, padx=PAD_X, pady=PAD_Y)
162
163     # Report Items
164     report_frame = Frame(frame)
165     report_frame.pack(fill=BOTH, expand=1)
166
167     yscrollbar = Scrollbar(report_frame)
168     yscrollbar.pack(side=RIGHT, fill=Y)
169     xscrollbar = Scrollbar(report_frame, orient=HORIZONTAL)
170     xscrollbar.pack(side=BOTTOM, fill=X)
171
172     tree = Treeview(report_frame, columns=[title_line], show='')
173     tree.pack(side=LEFT, fill=BOTH, expand=1)
174     tree.tag_configure('set_font', font=font)
175
176     tree.config(yscrollcommand=yscrollbar.set)
177     yscrollbar.config(command=tree.yview)
178     tree.config(xscrollcommand=xscrollbar.set)
179     xscrollbar.config(command=tree.xview)
180
181     self.display_report_items(tree, report_items)
182
183   def display_report_items(self, tree, report_items):
184     for report_item in report_items:
185       prefix_str = '+ ' if report_item.call_tree is not None else '  '
186       id = tree.insert(
187           '',
188           'end',
189           None,
190           values=[
191               prefix_str +
192               report_item.raw_line],
193           tag='set_font')
194       if report_item.call_tree is not None:
195         self.display_call_tree(tree, id, report_item.call_tree, 1)
196
197   def display_call_tree(self, tree, parent_id, node, indent):
198     id = parent_id
199     indent_str = '  ' * indent
200
201     if node.percentage != 100.0:
202       percentage_str = '%.2f%%' % node.percentage
203     else:
204       percentage_str = ''
205     first_open = True if node.percentage == 100.0 else False
206
207     for i in range(len(node.call_stack)):
208       s = indent_str
209       s += '+ ' if node.children else '  '
210       s += percentage_str if i == 0 else ' ' * len(percentage_str)
211       s += node.call_stack[i]
212       child_open = first_open if i == 0 else True
213       id = tree.insert(id, 'end', None, values=[s], open=child_open,
214                        tag='set_font')
215
216     for child in node.children:
217       self.display_call_tree(tree, id, child, indent + 1)
218
219
220 def display_report_file(report_file):
221   fh = open(report_file, 'r')
222   lines = fh.readlines()
223   fh.close()
224
225   lines = [x.rstrip() for x in lines]
226
227   blank_line_index = -1
228   for i in range(len(lines)):
229     if not lines[i]:
230       blank_line_index = i
231       break
232   assert blank_line_index != -1
233   assert blank_line_index + 1 < len(lines)
234
235   report_context = lines[:blank_line_index]
236   title_line = lines[blank_line_index + 1]
237   report_items = parse_report_items(lines[blank_line_index + 2:])
238
239   root = Tk()
240   ReportWindow(root, report_context, title_line, report_items)
241   root.mainloop()
242
243
244 def call_simpleperf_report(args, report_file):
245   output_fh = open(report_file, 'w')
246   args = ['simpleperf', 'report'] + args
247   subprocess.check_call(args, stdout=output_fh)
248   output_fh.close()
249
250
251 def main():
252   if len(sys.argv) == 2 and os.path.isfile(sys.argv[1]):
253     display_report_file(sys.argv[1])
254   else:
255     call_simpleperf_report(sys.argv[1:], 'perf.report')
256     display_report_file('perf.report')
257
258
259 if __name__ == '__main__':
260   main()