e3b4ffa9ba
- Log script sources with --log-function-events - Don't show confusing duration in graphs - Introduce separate compilation category - Log script details after deserialization - Log parse times for eval scripts - Display deserialized scripts and functions Change-Id: I58f3bf8efe3955632322f958716c36ad38761fd0 Bug: chromium:757467, chromium:850038 Reviewed-on: https://chromium-review.googlesource.com/1128082 Reviewed-by: Yang Guo <yangguo@chromium.org> Reviewed-by: Sathya Gunasekaran <gsathya@chromium.org> Commit-Queue: Camillo Bruni <cbruni@chromium.org> Cr-Commit-Position: refs/heads/master@{#54465}
418 lines
12 KiB
HTML
418 lines
12 KiB
HTML
<html>
|
|
<!--
|
|
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.
|
|
-->
|
|
|
|
<head>
|
|
<style>
|
|
html {
|
|
font-family: monospace;
|
|
}
|
|
|
|
.parse {
|
|
background-color: red;
|
|
border: 1px red solid;
|
|
}
|
|
|
|
.preparse {
|
|
background-color: orange;
|
|
border: 1px orange solid;
|
|
}
|
|
|
|
.resolution {
|
|
background-color: green;
|
|
border: 1px green solid;
|
|
}
|
|
|
|
.execution {
|
|
background-color: black;
|
|
border-left: 2px black solid;
|
|
z-index: -1;
|
|
}
|
|
|
|
.script {
|
|
margin-top: 1em;
|
|
overflow: visible;
|
|
clear: both;
|
|
border-top: 2px black dotted;
|
|
}
|
|
.script h3 {
|
|
height: 20px;
|
|
margin-bottom: 0.5em;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.script-details {
|
|
float: left;
|
|
}
|
|
|
|
.chart {
|
|
float: left;
|
|
margin-right: 2em;
|
|
}
|
|
|
|
.funktion-list {
|
|
float: left;
|
|
height: 400px;
|
|
}
|
|
|
|
.funktion-list > ul {
|
|
height: 80%;
|
|
overflow-y: scroll;
|
|
}
|
|
|
|
.funktion {
|
|
}
|
|
|
|
.script-size {
|
|
display: inline-flex;
|
|
background-color: #505050;
|
|
border-radius: 3px;
|
|
padding: 3px;
|
|
margin: 2px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-decoration: none;
|
|
color: white;
|
|
}
|
|
.script-size.eval {
|
|
background-color: #ee6300fc;
|
|
}
|
|
.script-size.streaming {
|
|
background-color: #008aff;
|
|
}
|
|
.script-size.deserialized {
|
|
background-color: #1fad00fc;
|
|
}
|
|
|
|
.script-details {
|
|
padding-right: 5px;
|
|
margin-right: 4px;
|
|
}
|
|
/* all but the last need a border */
|
|
.script-details:nth-last-child(n+2) {
|
|
border-right: 1px white solid;
|
|
}
|
|
|
|
.script-details.id {
|
|
min-width: 2em;
|
|
text-align: right;
|
|
}
|
|
</style>
|
|
<script src="./splaytree.js" type="text/javascript"></script>
|
|
<script src="./codemap.js" type="text/javascript"></script>
|
|
<script src="./csvparser.js" type="text/javascript"></script>
|
|
<script src="./consarray.js" type="text/javascript"></script>
|
|
<script src="./profile.js" type="text/javascript"></script>
|
|
<script src="./profile_view.js" type="text/javascript"></script>
|
|
<script src="./logreader.js" type="text/javascript"></script>
|
|
<script src="./arguments.js" type="text/javascript"></script>
|
|
<script src="./parse-processor.js" type="text/javascript"></script>
|
|
<script src="./SourceMap.js" type="text/javascript"></script>
|
|
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
|
|
<script type="text/javascript">
|
|
"use strict";
|
|
google.charts.load('current', {packages: ['corechart']});
|
|
|
|
function $(query) {
|
|
return document.querySelector(query);
|
|
}
|
|
|
|
function loadFile() {
|
|
let files = $('#uploadInput').files;
|
|
|
|
let file = files[0];
|
|
let reader = new FileReader();
|
|
|
|
reader.onload = function(evt) {
|
|
const kTimerName = 'parse log file';
|
|
console.time(kTimerName);
|
|
let parseProcessor = new ParseProcessor();
|
|
parseProcessor.processString(this.result);
|
|
console.timeEnd(kTimerName);
|
|
renderParseResults(parseProcessor);
|
|
document.parseProcessor = parseProcessor;
|
|
}
|
|
reader.readAsText(file);
|
|
}
|
|
|
|
function handleOnLoad() {
|
|
document.querySelector("#uploadInput").focus();
|
|
}
|
|
|
|
function createNode(tag, classNames) {
|
|
let node = document.createElement(tag);
|
|
if (classNames) {
|
|
if (Array.isArray(classNames)) {
|
|
node.classList.add(...classNames);
|
|
} else {
|
|
node.className = classNames;
|
|
}
|
|
}
|
|
return node;
|
|
}
|
|
|
|
function div(...args) {
|
|
return createNode('div', ...args);
|
|
}
|
|
|
|
function h1(string) {
|
|
let node = createNode('h1');
|
|
node.appendChild(text(string));
|
|
return node;
|
|
}
|
|
|
|
function h3(string, ...args) {
|
|
let node = createNode('h3', ...args);
|
|
if (string) node.appendChild(text(string));
|
|
return node;
|
|
}
|
|
|
|
function a(href, string, ...args) {
|
|
let link = createNode('a', ...args);
|
|
if (href.length) link.href = href;
|
|
if (string) link.appendChild(text(string));
|
|
return link;
|
|
}
|
|
|
|
function text(string) {
|
|
return document.createTextNode(string);
|
|
}
|
|
|
|
function delay(t) {
|
|
return new Promise(resolve => setTimeout(resolve, t));
|
|
}
|
|
|
|
function renderParseResults(parseProcessor) {
|
|
let result = $('#result');
|
|
// clear out all existing result pages;
|
|
result.innerHTML = '';
|
|
const start = parseProcessor.firstEventTimestamp;
|
|
const end = parseProcessor.lastEventTimestamp;
|
|
renderScript(result, parseProcessor.totalScript, start, end);
|
|
// Build up the graphs lazily to keep the page responsive.
|
|
parseProcessor.scripts.forEach(
|
|
script => renderScript(result, script, start, end));
|
|
renderScriptSizes(parseProcessor);
|
|
// Install an intersection observer to lazily load the graphs when the script
|
|
// div becomes visible for the first time.
|
|
var io = new IntersectionObserver((entries, observer) => {
|
|
entries.forEach(entry => {
|
|
if (entry.intersectionRatio == 0) return;
|
|
console.assert(!entry.target.querySelector('.graph'));
|
|
let target = entry.target;
|
|
appendGraph(target.script, target, start, end);
|
|
observer.unobserve(entry.target);
|
|
});
|
|
}, {rootMargin: '400px'});
|
|
document.querySelectorAll('.script').forEach(div => io.observe(div));
|
|
}
|
|
|
|
const kTimeFactor = 10;
|
|
const kHeight = 20;
|
|
const kFunktionTopOffset = 50;
|
|
|
|
function renderScript(result, script, start, end) {
|
|
// Filter out empty scripts.
|
|
if (script.isEmpty() || script.lastParseEvent == 0) return;
|
|
|
|
let scriptDiv = div('script');
|
|
scriptDiv.script = script;
|
|
|
|
let scriptTitle = h3();
|
|
let anchor = a("", 'Script #' + script.id);
|
|
anchor.name = "script"+script.id
|
|
scriptTitle.appendChild(anchor);
|
|
scriptDiv.appendChild(scriptTitle);
|
|
if (script.file) scriptTitle.appendChild(a(script.file, script.file));
|
|
let summary = createNode('pre', 'script-details');
|
|
summary.appendChild(text(script.summary));
|
|
scriptDiv.appendChild(summary);
|
|
result.appendChild(scriptDiv);
|
|
}
|
|
|
|
function renderScriptSizes(parseProcessor) {
|
|
let scriptsDiv = $('#scripts');
|
|
parseProcessor.scripts.forEach(
|
|
script => {
|
|
let scriptDiv = a('#script'+script.id, '', 'script-size');
|
|
let scriptId = div('script-details');
|
|
scriptId.classList.add('id');
|
|
scriptId.innerText = script.id;
|
|
scriptDiv.appendChild(scriptId);
|
|
let scriptSize = div('script-details');
|
|
scriptSize.innerText = BYTES(script.bytesTotal);
|
|
scriptDiv.appendChild(scriptSize);
|
|
let scriptUrl = div('script-details');
|
|
if (script.isEval) {
|
|
scriptUrl.innerText = "eval";
|
|
scriptDiv.classList.add('eval');
|
|
} else {
|
|
scriptUrl.innerText = script.file.split("/").pop();
|
|
}
|
|
if (script.isStreamingCompiled ) {
|
|
scriptDiv.classList.add('streaming');
|
|
} else if (script.deserializationTimestamp > 0) {
|
|
scriptDiv.classList.add('deserialized');
|
|
}
|
|
scriptDiv.appendChild(scriptUrl);
|
|
scriptDiv.style.width = script.bytesTotal * 0.001;
|
|
scriptsDiv.appendChild(scriptDiv);
|
|
});
|
|
}
|
|
|
|
const kMaxTime = 120 * kSecondsToMillis;
|
|
// Resolution of the graphs
|
|
const kTimeIncrement = 1;
|
|
const kSelectionTimespan = 2;
|
|
// TODO(cbruni): support compilation cache hit.
|
|
const series = [
|
|
['firstParseEvent', 'Any Parse', 'area'],
|
|
['execution', '1st Exec', 'area'],
|
|
['firstCompileEvent', 'Any Compile', 'area'],
|
|
['compile', 'Eager Compile'],
|
|
['lazyCompile', 'Lazy Compile'],
|
|
['parse', 'Parsing'],
|
|
['preparse', 'Preparse'],
|
|
['resolution', 'Preparse with Var. Resolution'],
|
|
['deserialization', 'Deserialization'],
|
|
['optimization', 'Optimize'],
|
|
];
|
|
const metricNames = series.map(each => each[0]);
|
|
// Display cumulative values (useuful for bytes).
|
|
const kCumulative = true;
|
|
// Include durations in the graphs.
|
|
const kUseDuration = false;
|
|
|
|
|
|
function appendGraph(script, parentNode, start, end) {
|
|
const timerLabel = 'graph script=' + script.id;
|
|
// TODO(cbruni): add support for network events
|
|
|
|
console.time(timerLabel);
|
|
let data = new google.visualization.DataTable();
|
|
data.addColumn('number', 'Duration');
|
|
// The series are interleave bytes processed, time spent and thus have two
|
|
// different vAxes.
|
|
let seriesOptions = [];
|
|
let colors = ['#4D4D4D', '#fff700', '#5DA5DA', '#FAA43A', '#60BD68',
|
|
'#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#F15854'];
|
|
series.forEach(([metric, description, type]) => {
|
|
let color = colors.shift();
|
|
// Add the bytes column.
|
|
data.addColumn('number', description);
|
|
let options = {targetAxisIndex: 0, color: color};
|
|
if (type == 'area') options.type = 'area';
|
|
seriesOptions.push(options)
|
|
// Add the time column.
|
|
if (kUseDuration) {
|
|
data.addColumn('number', description + ' Duration');
|
|
seriesOptions.push(
|
|
{targetAxisIndex: 1, color: color, lineDashStyle: [3, 2]});
|
|
}
|
|
});
|
|
|
|
const maxTime = Math.min(kMaxTime, end);
|
|
console.time('metrics');
|
|
let metricValues =
|
|
script.getAccumulatedTimeMetrics(metricNames , 0, maxTime, kTimeIncrement,
|
|
kCumulative, kUseDuration);
|
|
console.timeEnd('metrics');
|
|
// Make sure that the series added to the graph matches the returned values.
|
|
console.assert(metricValues[0].length == seriesOptions.length + 1);
|
|
data.addRows(metricValues);
|
|
|
|
let options = {
|
|
explorer: {
|
|
actions: ['dragToZoom', 'rightClickToReset'],
|
|
maxZoomIn: 0.01
|
|
},
|
|
hAxis: {
|
|
format: '#,###.##s'
|
|
},
|
|
vAxes: {
|
|
0: {title: 'Bytes Touched', format: 'short'},
|
|
1: {title: 'Duration', format: '#,###ms'}
|
|
},
|
|
height: 400,
|
|
width: 1000,
|
|
chartArea: {left: 70, top: 0, right: 160, height: "90%"},
|
|
// The first series should be a area chart (total bytes touched),
|
|
series: seriesOptions,
|
|
// everthing else is a line.
|
|
seriesType: 'line'
|
|
};
|
|
let graphNode = createNode('div', 'chart');
|
|
let listNode = createNode('div', 'funktion-list');
|
|
parentNode.appendChild(graphNode);
|
|
parentNode.appendChild(listNode);
|
|
let chart = new google.visualization.ComboChart(graphNode);
|
|
google.visualization.events.addListener(chart, 'select',
|
|
() => selectGraphPointHandler(chart, data, script, parentNode));
|
|
chart.draw(data, options);
|
|
// Add event listeners
|
|
console.timeEnd(timerLabel);
|
|
}
|
|
|
|
function selectGraphPointHandler(chart, data, script, parentNode) {
|
|
let selection = chart.getSelection();
|
|
if (selection.length <= 0) return;
|
|
// Display a list of funktions with events at the given time.
|
|
let {row, column} = selection[0];
|
|
if (row === null|| column === null) return;
|
|
const kEntrySize = kUseDuration ? 2 : 1;
|
|
let [metric, description] = series[((column-1)/ kEntrySize) | 0];
|
|
let time = data.getValue(row, 0);
|
|
let funktions = script.getFunktionsAtTime(
|
|
time * kSecondsToMillis, kSelectionTimespan, metric);
|
|
let oldList = parentNode.querySelector('.funktion-list');
|
|
parentNode.replaceChild(
|
|
createFunktionList(metric, description, time, funktions), oldList);
|
|
}
|
|
|
|
function createFunktionList(metric, description, time, funktions) {
|
|
let container = createNode('div', 'funktion-list');
|
|
container.appendChild(h3('Changes of "' + description + '" at ' +
|
|
time + 's: ' + funktions.length));
|
|
let listNode = createNode('ul');
|
|
funktions.forEach(funktion => {
|
|
let node = createNode('li', 'funktion');
|
|
node.funktion = funktion;
|
|
node.appendChild(text(funktion.toString(false) + " "));
|
|
let script = funktion.script;
|
|
if (script) {
|
|
node.appendChild(a("#script" + script.id, "in script " + script.id));
|
|
}
|
|
listNode.appendChild(node);
|
|
});
|
|
container.appendChild(listNode);
|
|
return container;
|
|
}
|
|
</script>
|
|
</head>
|
|
|
|
<body onload="handleOnLoad()">
|
|
<h1>BEHOLD, THIS IS PARSEROR!</h1>
|
|
|
|
<h2>Usage</h2>
|
|
Run your script with <code>--log-function-events</code> and upload <code>v8.log</code> on this page:<br/>
|
|
<code>/path/to/d8 --log-function-events your_script.js</code>
|
|
|
|
<h2>Data</h2>
|
|
<form name="fileForm">
|
|
<p>
|
|
<input id="uploadInput" type="file" name="files" onchange="loadFile();" accept=".log"> trace entries: <span id="count">0</span>
|
|
</p>
|
|
</form>
|
|
|
|
|
|
<h2>Scripts</h2>
|
|
<div id="scripts"></div>
|
|
|
|
<h2>Result</h2>
|
|
<div id="result"></div>
|
|
</body>
|
|
|
|
</html>
|