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 20There are two ways to use gui reporter. One way is to pass it a report file 21generated by simpleperf report command, and reporter will display it. The 22other ways is to pass it any arguments you want to use when calling 23simpleperf report command. The reporter will call `simpleperf report` to 24generate report file, and display it. 25""" 26 27import os.path 28import re 29import subprocess 30import sys 31from tkFont import * 32from Tkinter import * 33from ttk import * 34 35PAD_X = 3 36PAD_Y = 3 37 38 39class 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 69class 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 85def 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 140class 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 220def 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 244def 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 251def 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 259if __name__ == '__main__': 260 main() 261