[turbolizer] Add support for showing perf profiling information.

perf-turbo.py merges a perf data file and a turbofan trace file into a
single json object which can then be piped to a file and uploaded to
turbolizer to display the profiling data in the disassembly. With the
changes, turbolizer now shows the event counts for instruction in
percentage form and with heatmap-stype colouring. Multiple different
events can be recorded at once with a new drop-down menu to select which
event to view the counts of. The documentation has been updated with
instructions. Using the script is optional and turbolizer retains
previous functionality if a trace without profiling data is uploaded.

BUG=None

Review-Url: https://codereview.chromium.org/2174803002
Cr-Commit-Position: refs/heads/master@{#38124}
This commit is contained in:
Alexander.Gilday2 2016-07-28 02:39:21 -07:00 committed by Commit bot
parent 606e1ada17
commit 286e2b14a5
9 changed files with 224 additions and 23 deletions

View File

@ -1,20 +0,0 @@
Turbolizer is a HTML-based tool that visualizes optimized code along the various
phases of Turbofan's optimization pipeline, allowing easy navigation between
source code, Turbofan IR graphs, scheduled IR nodes and generated assembly code.
Turbolizer consumes .json files that are generated per-function by d8 by passing
the '--trace-turbo' command-line flag.
Host the turbolizer locally by starting a web server that serves the contents of
the turbolizer directory, e.g.:
cd src/tools/turbolizer
python -m SimpleHTTPServer 8000
Graph visualization and manipulation based on Mike Bostock's sample code for an
interactive tool for creating directed graphs. Original source is at
https://github.com/metacademy/directed-graph-creator and released under the
MIT/X license.
Icons dervied from the "White Olive Collection" created by Breezi released under
the Creative Commons BY license.

View File

@ -0,0 +1,62 @@
Turbolizer
==========
Turbolizer is a HTML-based tool that visualizes optimized code along the various
phases of Turbofan's optimization pipeline, allowing easy navigation between
source code, Turbofan IR graphs, scheduled IR nodes and generated assembly code.
Turbolizer consumes .json files that are generated per-function by d8 by passing
the '--trace-turbo' command-line flag.
Host the turbolizer locally by starting a web server that serves the contents of
the turbolizer directory, e.g.:
cd src/tools/turbolizer
python -m SimpleHTTPServer 8000
Optionally, profiling data generated by the perf tools in linux can be merged
with the .json files using the perf-turbo.py file included. The following
command is an example of using the perf script:
perf script -i perf.data.jitted -s perf-turbo.py turbo-main.json
The output of the above command is a json object that can be piped to a file
which, when uploaded to turbolizer, will display the event counts from perf next
to each instruction in the disassembly. Further detail can be found in the
bottom of this document under "Using Perf with Turbo."
Using the python interface in perf script requires python-dev to be installed
and perf be recompiled with python support enabled. Once recompiled, the
variable PERF_EXEC_PATH must be set to the location of the recompiled perf
binaries.
Graph visualization and manipulation based on Mike Bostock's sample code for an
interactive tool for creating directed graphs. Original source is at
https://github.com/metacademy/directed-graph-creator and released under the
MIT/X license.
Icons derived from the "White Olive Collection" created by Breezi released under
the Creative Commons BY license.
Using Perf with Turbo
---------------------
In order to generate perf data that matches exactly with the turbofan trace, you
must use either a debug build of v8 or a release build with the flag
'disassembler=on'. This flag ensures that the '--trace-turbo' will output the
necessary disassembly for linking with the perf profile.
The basic example of generating the required data is as follows:
perf record -k mono /path/to/d8 --turbo --trace-turbo --perf-prof main.js
perf inject -j -i perf.data -o perf.data.jitted
perf script -i perf.data.jitted -s perf-turbo.py turbo-main.json
These commands combined will run and profile d8, merge the output into a single
'perf.data.jitted' file, then take the event data from that and link them to the
disassembly in the 'turbo-main.json'. Note that, as above, the output of the
script command must be piped to a file for uploading to turbolizer.
There are many options that can be added to the first command, for example '-e'
can be used to specify the counting of specific events (default: cycles), as
well as '--cpu' to specify which CPU to sample.

View File

@ -20,3 +20,5 @@ var DISASSEMBLY_COLLAPSE_ID = 'disassembly-shrink';
var DISASSEMBLY_EXPAND_ID = 'disassembly-expand';
var COLLAPSE_PANE_BUTTON_VISIBLE = 'button-input';
var COLLAPSE_PANE_BUTTON_INVISIBLE = 'button-input-invisible';
var PROF_HIGH = 5;
var PROF_MED = 0.5;

View File

@ -9,6 +9,9 @@ class DisassemblyView extends TextView {
super(id, broker, null, false);
this.pos_start = -1;
this.pos_lines = null;
this.addr_event_counts = null;
this.total_event_counts = null;
let view = this;
let ADDRESS_STYLE = {
css: 'tag',
@ -143,6 +146,11 @@ class DisassemblyView extends TextView {
return result;
}
initializeContent(data, rememberedSelection) {
this.data = data;
super.initializeContent(data, rememberedSelection);
}
initializeCode(sourceText, sourcePosition) {
let view = this;
view.pos_lines = new Array();
@ -153,7 +161,7 @@ class DisassemblyView extends TextView {
let base = sourcePosition;
let current = 0;
let source_lines = sourceText.split("\n");
for (i=1; i < source_lines.length; i++) {
for (let i = 1; i < source_lines.length; i++) {
// Add 1 for newline character that is split off.
current += source_lines[i-1].length + 1;
view.pos_lines[i] = base + current;
@ -161,6 +169,29 @@ class DisassemblyView extends TextView {
}
}
initializePerfProfile(eventCounts) {
let view = this;
if (eventCounts !== undefined) {
view.addr_event_counts = eventCounts;
view.total_event_counts = {};
for (var ev_name in view.addr_event_counts) {
let keys = Object.keys(view.addr_event_counts[ev_name]);
let values = keys.map(key => view.addr_event_counts[ev_name][key]);
view.total_event_counts[ev_name] = values.reduce((a, b) => a + b);
}
}
else {
view.addr_event_counts = null;
view.total_event_counts = null;
}
}
// Shorten decimals and remove trailing zeroes for readability.
humanize(num) {
return num.toFixed(3).replace(/\.?0+$/, "") + "%";
}
processLine(line) {
let view = this;
let func = function(match, p1, p2, p3) {
@ -174,6 +205,35 @@ class DisassemblyView extends TextView {
};
line = line.replace(view.SOURCE_POSITION_HEADER_REGEX, func);
let fragments = super.processLine(line);
// Add profiling data per instruction if available.
if (view.total_event_counts) {
let event_selector = document.getElementById('event-selector');
if (event_selector.length !== 0) {
let event = event_selector.value;
let matches = /^(0x[0-9a-fA-F]+)\s+\d+\s+[0-9a-fA-F]+/.exec(line);
if (matches) {
let count = view.addr_event_counts[event][matches[1]];
let str = "";
let css_cls = undefined;
if(count !== undefined) {
let perc = count / view.total_event_counts[event] * 100;
str = "(" + view.humanize(perc) + ") ";
css_cls = "prof-low";
if(perc > PROF_HIGH)
css_cls = "prof-high";
else if(perc > PROF_MED)
css_cls = "prof-med";
}
// Pad extra spaces to keep alignment for all instructions.
str = (" ".repeat(10) + str).slice(-10);
fragments.splice(0, 0, view.createFragment(str, css_cls));
}
}
}
return fragments;
}
}

View File

@ -51,6 +51,9 @@
</text></svg></div>
</div>
<div id="right">
<span id="disassembly-toolbox">
<select id="event-selector"></select>
</span>
<div id='disassembly'>
<pre id='disassembly-text-pre' class='prettyprint prettyprinted'>
<ul id='disassembly-list' class='nolinenums noindent'>

View File

@ -0,0 +1,56 @@
# Copyright 2016 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os
import sys
import json
import re
import argparse
sys.path.append(os.environ['PERF_EXEC_PATH'] + \
'/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
from perf_trace_context import *
from Core import *
def trace_begin():
json_obj['eventCounts'] = {}
prog = re.compile(r'0x[0-9a-fA-F]+')
for phase in reversed(json_obj['phases']):
if phase['name'] == "disassembly":
for line in phase['data'].splitlines():
result = re.match(prog, line)
if result:
known_addrs.add(result.group(0))
def trace_end():
print json.dumps(json_obj)
def process_event(param_dict):
addr = "0x%x" % int(param_dict['sample']['ip'])
# Only count samples that belong to the function
if addr not in known_addrs:
return
ev_name = param_dict['ev_name']
if ev_name not in json_obj['eventCounts']:
json_obj['eventCounts'][ev_name] = {}
if addr not in json_obj['eventCounts'][ev_name]:
json_obj['eventCounts'][ev_name][addr] = 0
json_obj['eventCounts'][ev_name][addr] += 1
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Perf script to merge profiling data with turbofan compiler "
"traces.")
parser.add_argument("file_name", metavar="JSON File",
help="turbo trace json file.")
args = parser.parse_args()
with open(args.file_name, 'r') as json_file:
json_obj = json.load(json_file)
known_addrs = set()

View File

@ -295,6 +295,16 @@ span.linkable-text:hover {
background: rgba(100%, 100%, 100%, 0.7);
}
#disassembly-toolbox {
position: relative;
top: 1em;
left: 0.7em;
border: 2px solid #eee8d5;
border-radius: 5px;
padding: 0.7em;
z-index: 5;
}
#load-file {
position: absolute;
top: 0;
@ -311,7 +321,18 @@ span.linkable-text:hover {
padding: 0.5em;
}
#hidden-file-upload{
#hidden-file-upload {
display: none;
}
.prof-low {
color: #888;
}
.prof-med {
color: #080;
}
.prof-high {
color: #800;
}

View File

@ -184,6 +184,15 @@ document.onload = (function(d3){
selectMenu.add(optionElement, null);
}
}
var eventMenu = document.getElementById('event-selector');
eventMenu.innerHTML = '';
for (var event in jsonObj.eventCounts) {
var optionElement = document.createElement("option");
optionElement.text = event;
eventMenu.add(optionElement, null);
}
disassemblyView.initializePerfProfile(jsonObj.eventCounts);
disassemblyView.setNodePositionMap(jsonObj.nodePositions);
disassemblyView.show(disassemblyPhase.data, null);
@ -205,6 +214,10 @@ document.onload = (function(d3){
displayPhase(jsonObj.phases[selectMenu.selectedIndex]);
}
eventMenu.onchange = function(item) {
disassemblyView.show(disassemblyView.data, null);
}
fitPanesToParents();
d3.select("#search-input").attr("value", window.sessionStorage.getItem("lastSearch") || "");

View File

@ -26,7 +26,11 @@ class View {
resizeToParent() {
var view = this;
var documentElement = document.documentElement;
var y = this.parentNode.clientHeight || documentElement.clientHeight;
var y;
if (this.parentNode.clientHeight)
y = Math.max(this.parentNode.clientHeight, documentElement.clientHeight);
else
y = documentElement.clientHeight;
this.parentNode.style.height = y + 'px';
}