3 # Copyright (C) 2015 The Android Open Source Project
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
18 """Simpleperf gui reporter: provide gui interface for simpleperf report command.
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.
39 class CallTreeNode(object):
41 """Representing a node in call-graph."""
43 def __init__(self, percentage, function_name):
44 self.percentage = percentage
45 self.call_stack = [function_name]
48 def add_call(self, function_name):
49 self.call_stack.append(function_name)
51 def add_child(self, node):
52 self.children.append(node)
56 return '\n'.join(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])
69 class ReportItem(object):
71 """Representing one item in report, may contain a CallTree."""
73 def __init__(self, raw_line):
74 self.raw_line = raw_line
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)
85 def parse_report_items(lines):
87 cur_report_item = None
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.
101 for i in range(len(line)):
103 if not vertical_columns or vertical_columns[-1] < i:
104 vertical_columns.append(i)
106 if not line.strip('| \t'):
108 if line.find('-') == -1:
109 line = line.strip('| \t')
111 last_node.add_call(function_name)
115 for i in range(len(vertical_columns)):
116 if pos >= vertical_columns[i]:
120 line = line.strip('|- \t')
121 m = re.search(r'^([\d\.]+)%[-\s]+(.+)$', line)
123 percentage = float(m.group(1))
124 function_name = m.group(2)
129 node = CallTreeNode(percentage, function_name)
131 cur_report_item.call_tree = node
133 call_tree_stack[depth - 1].add_child(node)
134 call_tree_stack[depth] = node
140 class ReportWindow(object):
142 """A window used to display report file."""
144 def __init__(self, master, report_context, title_line, report_items):
145 frame = Frame(master)
146 frame.pack(fill=BOTH, expand=1)
148 font = Font(family='courier', size=10)
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)
156 label = Label(frame, text='', font=font)
157 label.pack(anchor=W, padx=PAD_X, pady=PAD_Y)
160 label = Label(frame, text=' ' + title_line, font=font)
161 label.pack(anchor=W, padx=PAD_X, pady=PAD_Y)
164 report_frame = Frame(frame)
165 report_frame.pack(fill=BOTH, expand=1)
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)
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)
176 tree.config(yscrollcommand=yscrollbar.set)
177 yscrollbar.config(command=tree.yview)
178 tree.config(xscrollcommand=xscrollbar.set)
179 xscrollbar.config(command=tree.xview)
181 self.display_report_items(tree, report_items)
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 ' '
192 report_item.raw_line],
194 if report_item.call_tree is not None:
195 self.display_call_tree(tree, id, report_item.call_tree, 1)
197 def display_call_tree(self, tree, parent_id, node, indent):
199 indent_str = ' ' * indent
201 if node.percentage != 100.0:
202 percentage_str = '%.2f%%' % node.percentage
205 first_open = True if node.percentage == 100.0 else False
207 for i in range(len(node.call_stack)):
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,
216 for child in node.children:
217 self.display_call_tree(tree, id, child, indent + 1)
220 def display_report_file(report_file):
221 fh = open(report_file, 'r')
222 lines = fh.readlines()
225 lines = [x.rstrip() for x in lines]
227 blank_line_index = -1
228 for i in range(len(lines)):
232 assert blank_line_index != -1
233 assert blank_line_index + 1 < len(lines)
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:])
240 ReportWindow(root, report_context, title_line, report_items)
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)
252 if len(sys.argv) == 2 and os.path.isfile(sys.argv[1]):
253 display_report_file(sys.argv[1])
255 call_simpleperf_report(sys.argv[1:], 'perf.report')
256 display_report_file('perf.report')
259 if __name__ == '__main__':