report.py revision 182d8a03949aca735fd95953a3efad20068b1bad
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 * 34from utils import * 35 36PAD_X = 3 37PAD_Y = 3 38 39 40class CallTreeNode(object): 41 42 """Representing a node in call-graph.""" 43 44 def __init__(self, percentage, function_name): 45 self.percentage = percentage 46 self.call_stack = [function_name] 47 self.children = [] 48 49 def add_call(self, function_name): 50 self.call_stack.append(function_name) 51 52 def add_child(self, node): 53 self.children.append(node) 54 55 def __str__(self): 56 strs = self.dump() 57 return '\n'.join(strs) 58 59 def dump(self): 60 strs = [] 61 strs.append('CallTreeNode percentage = %.2f' % self.percentage) 62 for function_name in self.call_stack: 63 strs.append(' %s' % function_name) 64 for child in self.children: 65 child_strs = child.dump() 66 strs.extend([' ' + x for x in child_strs]) 67 return strs 68 69 70class ReportItem(object): 71 72 """Representing one item in report, may contain a CallTree.""" 73 74 def __init__(self, raw_line): 75 self.raw_line = raw_line 76 self.call_tree = None 77 78 def __str__(self): 79 strs = [] 80 strs.append('ReportItem (raw_line %s)' % self.raw_line) 81 if self.call_tree is not None: 82 strs.append('%s' % self.call_tree) 83 return '\n'.join(strs) 84 85 86def parse_report_items(lines): 87 report_items = [] 88 cur_report_item = None 89 call_tree_stack = {} 90 vertical_columns = [] 91 last_node = None 92 93 for line in lines: 94 if not line: 95 continue 96 if not line[0].isspace(): 97 cur_report_item = ReportItem(line) 98 report_items.append(cur_report_item) 99 # Each report item can have different column depths. 100 vertical_columns = [] 101 else: 102 for i in range(len(line)): 103 if line[i] == '|': 104 if not vertical_columns or vertical_columns[-1] < i: 105 vertical_columns.append(i) 106 107 if not line.strip('| \t'): 108 continue 109 if line.find('-') == -1: 110 line = line.strip('| \t') 111 function_name = line 112 last_node.add_call(function_name) 113 else: 114 pos = line.find('-') 115 depth = -1 116 for i in range(len(vertical_columns)): 117 if pos >= vertical_columns[i]: 118 depth = i 119 assert depth != -1 120 121 line = line.strip('|- \t') 122 m = re.search(r'^([\d\.]+)%[-\s]+(.+)$', line) 123 if m: 124 percentage = float(m.group(1)) 125 function_name = m.group(2) 126 else: 127 percentage = 100.0 128 function_name = line 129 130 node = CallTreeNode(percentage, function_name) 131 if depth == 0: 132 cur_report_item.call_tree = node 133 else: 134 call_tree_stack[depth - 1].add_child(node) 135 call_tree_stack[depth] = node 136 last_node = node 137 138 return report_items 139 140 141class ReportWindow(object): 142 143 """A window used to display report file.""" 144 145 def __init__(self, master, report_context, title_line, report_items): 146 frame = Frame(master) 147 frame.pack(fill=BOTH, expand=1) 148 149 font = Font(family='courier', size=10) 150 151 # Report Context 152 for line in report_context: 153 label = Label(frame, text=line, font=font) 154 label.pack(anchor=W, padx=PAD_X, pady=PAD_Y) 155 156 # Space 157 label = Label(frame, text='', font=font) 158 label.pack(anchor=W, padx=PAD_X, pady=PAD_Y) 159 160 # Title 161 label = Label(frame, text=' ' + title_line, font=font) 162 label.pack(anchor=W, padx=PAD_X, pady=PAD_Y) 163 164 # Report Items 165 report_frame = Frame(frame) 166 report_frame.pack(fill=BOTH, expand=1) 167 168 yscrollbar = Scrollbar(report_frame) 169 yscrollbar.pack(side=RIGHT, fill=Y) 170 xscrollbar = Scrollbar(report_frame, orient=HORIZONTAL) 171 xscrollbar.pack(side=BOTTOM, fill=X) 172 173 tree = Treeview(report_frame, columns=[title_line], show='') 174 tree.pack(side=LEFT, fill=BOTH, expand=1) 175 tree.tag_configure('set_font', font=font) 176 177 tree.config(yscrollcommand=yscrollbar.set) 178 yscrollbar.config(command=tree.yview) 179 tree.config(xscrollcommand=xscrollbar.set) 180 xscrollbar.config(command=tree.xview) 181 182 self.display_report_items(tree, report_items) 183 184 def display_report_items(self, tree, report_items): 185 for report_item in report_items: 186 prefix_str = '+ ' if report_item.call_tree is not None else ' ' 187 id = tree.insert( 188 '', 189 'end', 190 None, 191 values=[ 192 prefix_str + 193 report_item.raw_line], 194 tag='set_font') 195 if report_item.call_tree is not None: 196 self.display_call_tree(tree, id, report_item.call_tree, 1) 197 198 def display_call_tree(self, tree, parent_id, node, indent): 199 id = parent_id 200 indent_str = ' ' * indent 201 202 if node.percentage != 100.0: 203 percentage_str = '%.2f%%' % node.percentage 204 else: 205 percentage_str = '' 206 first_open = True if node.percentage == 100.0 else False 207 208 for i in range(len(node.call_stack)): 209 s = indent_str 210 s += '+ ' if node.children else ' ' 211 s += percentage_str if i == 0 else ' ' * len(percentage_str) 212 s += node.call_stack[i] 213 child_open = first_open if i == 0 else True 214 id = tree.insert(id, 'end', None, values=[s], open=child_open, 215 tag='set_font') 216 217 for child in node.children: 218 self.display_call_tree(tree, id, child, indent + 1) 219 220 221def display_report_file(report_file): 222 fh = open(report_file, 'r') 223 lines = fh.readlines() 224 fh.close() 225 226 lines = [x.rstrip() for x in lines] 227 228 blank_line_index = -1 229 for i in range(len(lines)): 230 if not lines[i]: 231 blank_line_index = i 232 break 233 assert blank_line_index != -1 234 assert blank_line_index + 1 < len(lines) 235 236 report_context = lines[:blank_line_index] 237 title_line = lines[blank_line_index + 1] 238 report_items = parse_report_items(lines[blank_line_index + 2:]) 239 240 root = Tk() 241 ReportWindow(root, report_context, title_line, report_items) 242 root.mainloop() 243 244 245def call_simpleperf_report(args, report_file): 246 output_fh = open(report_file, 'w') 247 simpleperf_path = get_host_binary_path('simpleperf') 248 args = [simpleperf_path, 'report'] + args 249 subprocess.check_call(args, stdout=output_fh) 250 output_fh.close() 251 252 253def main(): 254 if len(sys.argv) == 2 and os.path.isfile(sys.argv[1]): 255 display_report_file(sys.argv[1]) 256 else: 257 call_simpleperf_report(sys.argv[1:], 'perf.report') 258 display_report_file('perf.report') 259 260 261if __name__ == '__main__': 262 main() 263