diff options
author | Yabin Cui <yabinc@google.com> | 2017-01-05 17:58:19 -0800 |
---|---|---|
committer | Yabin Cui <yabinc@google.com> | 2017-01-06 09:52:32 -0800 |
commit | 182d8a03949aca735fd95953a3efad20068b1bad (patch) | |
tree | 678535ed467bcbe4cab40836b44b282f5bb5a5a7 /simpleperf/scripts/report.py | |
parent | 8d367acde0d5fd09a3427703dc11583d44eb8199 (diff) | |
download | extras-182d8a03949aca735fd95953a3efad20068b1bad.tar.gz |
simpleperf: add scripts to sdk build.
Bug: http://b/32834638
Test: make sdk.
Change-Id: Iffc2649dc131c73758db8727912c83c078d92071
Diffstat (limited to 'simpleperf/scripts/report.py')
-rw-r--r-- | simpleperf/scripts/report.py | 262 |
1 files changed, 262 insertions, 0 deletions
diff --git a/simpleperf/scripts/report.py b/simpleperf/scripts/report.py new file mode 100644 index 00000000..b58ac6bc --- /dev/null +++ b/simpleperf/scripts/report.py @@ -0,0 +1,262 @@ +#!/usr/bin/env python +# +# Copyright (C) 2015 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Simpleperf gui reporter: provide gui interface for simpleperf report command. + +There are two ways to use gui reporter. One way is to pass it a report file +generated by simpleperf report command, and reporter will display it. The +other ways is to pass it any arguments you want to use when calling +simpleperf report command. The reporter will call `simpleperf report` to +generate report file, and display it. +""" + +import os.path +import re +import subprocess +import sys +from tkFont import * +from Tkinter import * +from ttk import * +from utils import * + +PAD_X = 3 +PAD_Y = 3 + + +class CallTreeNode(object): + + """Representing a node in call-graph.""" + + def __init__(self, percentage, function_name): + self.percentage = percentage + self.call_stack = [function_name] + self.children = [] + + def add_call(self, function_name): + self.call_stack.append(function_name) + + def add_child(self, node): + self.children.append(node) + + def __str__(self): + strs = self.dump() + return '\n'.join(strs) + + def dump(self): + strs = [] + strs.append('CallTreeNode percentage = %.2f' % self.percentage) + for function_name in self.call_stack: + strs.append(' %s' % function_name) + for child in self.children: + child_strs = child.dump() + strs.extend([' ' + x for x in child_strs]) + return strs + + +class ReportItem(object): + + """Representing one item in report, may contain a CallTree.""" + + def __init__(self, raw_line): + self.raw_line = raw_line + self.call_tree = None + + def __str__(self): + strs = [] + strs.append('ReportItem (raw_line %s)' % self.raw_line) + if self.call_tree is not None: + strs.append('%s' % self.call_tree) + return '\n'.join(strs) + + +def parse_report_items(lines): + report_items = [] + cur_report_item = None + call_tree_stack = {} + vertical_columns = [] + last_node = None + + for line in lines: + if not line: + continue + if not line[0].isspace(): + cur_report_item = ReportItem(line) + report_items.append(cur_report_item) + # Each report item can have different column depths. + vertical_columns = [] + else: + for i in range(len(line)): + if line[i] == '|': + if not vertical_columns or vertical_columns[-1] < i: + vertical_columns.append(i) + + if not line.strip('| \t'): + continue + if line.find('-') == -1: + line = line.strip('| \t') + function_name = line + last_node.add_call(function_name) + else: + pos = line.find('-') + depth = -1 + for i in range(len(vertical_columns)): + if pos >= vertical_columns[i]: + depth = i + assert depth != -1 + + line = line.strip('|- \t') + m = re.search(r'^([\d\.]+)%[-\s]+(.+)$', line) + if m: + percentage = float(m.group(1)) + function_name = m.group(2) + else: + percentage = 100.0 + function_name = line + + node = CallTreeNode(percentage, function_name) + if depth == 0: + cur_report_item.call_tree = node + else: + call_tree_stack[depth - 1].add_child(node) + call_tree_stack[depth] = node + last_node = node + + return report_items + + +class ReportWindow(object): + + """A window used to display report file.""" + + def __init__(self, master, report_context, title_line, report_items): + frame = Frame(master) + frame.pack(fill=BOTH, expand=1) + + font = Font(family='courier', size=10) + + # Report Context + for line in report_context: + label = Label(frame, text=line, font=font) + label.pack(anchor=W, padx=PAD_X, pady=PAD_Y) + + # Space + label = Label(frame, text='', font=font) + label.pack(anchor=W, padx=PAD_X, pady=PAD_Y) + + # Title + label = Label(frame, text=' ' + title_line, font=font) + label.pack(anchor=W, padx=PAD_X, pady=PAD_Y) + + # Report Items + report_frame = Frame(frame) + report_frame.pack(fill=BOTH, expand=1) + + yscrollbar = Scrollbar(report_frame) + yscrollbar.pack(side=RIGHT, fill=Y) + xscrollbar = Scrollbar(report_frame, orient=HORIZONTAL) + xscrollbar.pack(side=BOTTOM, fill=X) + + tree = Treeview(report_frame, columns=[title_line], show='') + tree.pack(side=LEFT, fill=BOTH, expand=1) + tree.tag_configure('set_font', font=font) + + tree.config(yscrollcommand=yscrollbar.set) + yscrollbar.config(command=tree.yview) + tree.config(xscrollcommand=xscrollbar.set) + xscrollbar.config(command=tree.xview) + + self.display_report_items(tree, report_items) + + def display_report_items(self, tree, report_items): + for report_item in report_items: + prefix_str = '+ ' if report_item.call_tree is not None else ' ' + id = tree.insert( + '', + 'end', + None, + values=[ + prefix_str + + report_item.raw_line], + tag='set_font') + if report_item.call_tree is not None: + self.display_call_tree(tree, id, report_item.call_tree, 1) + + def display_call_tree(self, tree, parent_id, node, indent): + id = parent_id + indent_str = ' ' * indent + + if node.percentage != 100.0: + percentage_str = '%.2f%%' % node.percentage + else: + percentage_str = '' + first_open = True if node.percentage == 100.0 else False + + for i in range(len(node.call_stack)): + s = indent_str + s += '+ ' if node.children else ' ' + s += percentage_str if i == 0 else ' ' * len(percentage_str) + s += node.call_stack[i] + child_open = first_open if i == 0 else True + id = tree.insert(id, 'end', None, values=[s], open=child_open, + tag='set_font') + + for child in node.children: + self.display_call_tree(tree, id, child, indent + 1) + + +def display_report_file(report_file): + fh = open(report_file, 'r') + lines = fh.readlines() + fh.close() + + lines = [x.rstrip() for x in lines] + + blank_line_index = -1 + for i in range(len(lines)): + if not lines[i]: + blank_line_index = i + break + assert blank_line_index != -1 + assert blank_line_index + 1 < len(lines) + + report_context = lines[:blank_line_index] + title_line = lines[blank_line_index + 1] + report_items = parse_report_items(lines[blank_line_index + 2:]) + + root = Tk() + ReportWindow(root, report_context, title_line, report_items) + root.mainloop() + + +def call_simpleperf_report(args, report_file): + output_fh = open(report_file, 'w') + simpleperf_path = get_host_binary_path('simpleperf') + args = [simpleperf_path, 'report'] + args + subprocess.check_call(args, stdout=output_fh) + output_fh.close() + + +def main(): + if len(sys.argv) == 2 and os.path.isfile(sys.argv[1]): + display_report_file(sys.argv[1]) + else: + call_simpleperf_report(sys.argv[1:], 'perf.report') + display_report_file('perf.report') + + +if __name__ == '__main__': + main() |