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 85class EventReport(object): 86 87 """Representing report for one event attr.""" 88 89 def __init__(self, common_report_context): 90 self.context = common_report_context[:] 91 self.title_line = None 92 self.report_items = [] 93 94 95def parse_event_reports(lines): 96 # Parse common report context 97 common_report_context = [] 98 line_id = 0 99 while line_id < len(lines): 100 line = lines[line_id] 101 if not line or line.find('Event:') == 0: 102 break 103 common_report_context.append(line) 104 line_id += 1 105 106 event_reports = [] 107 in_report_context = True 108 cur_event_report = EventReport(common_report_context) 109 cur_report_item = None 110 call_tree_stack = {} 111 vertical_columns = [] 112 last_node = None 113 114 for line in lines[line_id:]: 115 if not line: 116 in_report_context = not in_report_context 117 if in_report_context: 118 cur_event_report = EventReport(common_report_context) 119 continue 120 121 if in_report_context: 122 cur_event_report.context.append(line) 123 if line.find('Event:') == 0: 124 event_reports.append(cur_event_report) 125 continue 126 127 if cur_event_report.title_line is None: 128 cur_event_report.title_line = line 129 elif not line[0].isspace(): 130 cur_report_item = ReportItem(line) 131 cur_event_report.report_items.append(cur_report_item) 132 # Each report item can have different column depths. 133 vertical_columns = [] 134 else: 135 for i in range(len(line)): 136 if line[i] == '|': 137 if not vertical_columns or vertical_columns[-1] < i: 138 vertical_columns.append(i) 139 140 if not line.strip('| \t'): 141 continue 142 if line.find('-') == -1: 143 line = line.strip('| \t') 144 function_name = line 145 last_node.add_call(function_name) 146 else: 147 pos = line.find('-') 148 depth = -1 149 for i in range(len(vertical_columns)): 150 if pos >= vertical_columns[i]: 151 depth = i 152 assert depth != -1 153 154 line = line.strip('|- \t') 155 m = re.search(r'^([\d\.]+)%[-\s]+(.+)$', line) 156 if m: 157 percentage = float(m.group(1)) 158 function_name = m.group(2) 159 else: 160 percentage = 100.0 161 function_name = line 162 163 node = CallTreeNode(percentage, function_name) 164 if depth == 0: 165 cur_report_item.call_tree = node 166 else: 167 call_tree_stack[depth - 1].add_child(node) 168 call_tree_stack[depth] = node 169 last_node = node 170 171 return event_reports 172 173 174class ReportWindow(object): 175 176 """A window used to display report file.""" 177 178 def __init__(self, master, report_context, title_line, report_items): 179 frame = Frame(master) 180 frame.pack(fill=BOTH, expand=1) 181 182 font = Font(family='courier', size=10) 183 184 # Report Context 185 for line in report_context: 186 label = Label(frame, text=line, font=font) 187 label.pack(anchor=W, padx=PAD_X, pady=PAD_Y) 188 189 # Space 190 label = Label(frame, text='', font=font) 191 label.pack(anchor=W, padx=PAD_X, pady=PAD_Y) 192 193 # Title 194 label = Label(frame, text=' ' + title_line, font=font) 195 label.pack(anchor=W, padx=PAD_X, pady=PAD_Y) 196 197 # Report Items 198 report_frame = Frame(frame) 199 report_frame.pack(fill=BOTH, expand=1) 200 201 yscrollbar = Scrollbar(report_frame) 202 yscrollbar.pack(side=RIGHT, fill=Y) 203 xscrollbar = Scrollbar(report_frame, orient=HORIZONTAL) 204 xscrollbar.pack(side=BOTTOM, fill=X) 205 206 tree = Treeview(report_frame, columns=[title_line], show='') 207 tree.pack(side=LEFT, fill=BOTH, expand=1) 208 tree.tag_configure('set_font', font=font) 209 210 tree.config(yscrollcommand=yscrollbar.set) 211 yscrollbar.config(command=tree.yview) 212 tree.config(xscrollcommand=xscrollbar.set) 213 xscrollbar.config(command=tree.xview) 214 215 self.display_report_items(tree, report_items) 216 217 def display_report_items(self, tree, report_items): 218 for report_item in report_items: 219 prefix_str = '+ ' if report_item.call_tree is not None else ' ' 220 id = tree.insert( 221 '', 222 'end', 223 None, 224 values=[ 225 prefix_str + 226 report_item.raw_line], 227 tag='set_font') 228 if report_item.call_tree is not None: 229 self.display_call_tree(tree, id, report_item.call_tree, 1) 230 231 def display_call_tree(self, tree, parent_id, node, indent): 232 id = parent_id 233 indent_str = ' ' * indent 234 235 if node.percentage != 100.0: 236 percentage_str = '%.2f%%' % node.percentage 237 else: 238 percentage_str = '' 239 first_open = True if node.percentage == 100.0 else False 240 241 for i in range(len(node.call_stack)): 242 s = indent_str 243 s += '+ ' if node.children else ' ' 244 s += percentage_str if i == 0 else ' ' * len(percentage_str) 245 s += node.call_stack[i] 246 child_open = first_open if i == 0 else True 247 id = tree.insert(id, 'end', None, values=[s], open=child_open, 248 tag='set_font') 249 250 for child in node.children: 251 self.display_call_tree(tree, id, child, indent + 1) 252 253 254def display_report_file(report_file): 255 fh = open(report_file, 'r') 256 lines = fh.readlines() 257 fh.close() 258 259 lines = [x.rstrip() for x in lines] 260 event_reports = parse_event_reports(lines) 261 262 if event_reports: 263 root = Tk() 264 for i in range(len(event_reports)): 265 report = event_reports[i] 266 parent = root if i == 0 else Toplevel(root) 267 ReportWindow(parent, report.context, report.title_line, report.report_items) 268 root.mainloop() 269 270 271def call_simpleperf_report(args, report_file): 272 output_fh = open(report_file, 'w') 273 simpleperf_path = get_host_binary_path('simpleperf') 274 args = [simpleperf_path, 'report'] + args 275 subprocess.check_call(args, stdout=output_fh) 276 output_fh.close() 277 278 279def main(): 280 if len(sys.argv) == 2 and os.path.isfile(sys.argv[1]): 281 display_report_file(sys.argv[1]) 282 else: 283 call_simpleperf_report(sys.argv[1:], 'perf.report') 284 display_report_file('perf.report') 285 286 287if __name__ == '__main__': 288 main() 289