[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:
parent
606e1ada17
commit
286e2b14a5
@ -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.
|
62
tools/turbolizer/README.md
Normal file
62
tools/turbolizer/README.md
Normal 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.
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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'>
|
||||
|
56
tools/turbolizer/perf-turbo.py
Normal file
56
tools/turbolizer/perf-turbo.py
Normal 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()
|
@ -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;
|
||||
}
|
@ -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") || "");
|
||||
|
@ -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';
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user