Commit 27b226b7 authored by Camillo Bruni's avatar Camillo Bruni Committed by Commit Bot

[tools] Remove map-processor

- The command line tool never fully worked
- All the main features of the map-processor are now available in
  the system-analyzer

Bug: v8:10644
Change-Id: Ic55b1d6de561079b079045097856a3b4e5f4bb95
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2497178Reviewed-by: 's avatarVictor Gomes <victorgomes@chromium.org>
Commit-Queue: Camillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#70764}
parent 87bc38e3
......@@ -63,6 +63,10 @@ dd, dt {
<h1>Welcome to the V8 Tools Landing Page</h1>
<p>Search through this page to find about the V8 tools to debug, trace and analyze the log files.</p>
<dl class="grid-container">
<div class="card">
<dt><a href="./system-analyzer/index.html">System Analyzer</a></dt>
<dd>A unified web interface to trace, debug and analyse patterns of how Maps/ICs are created in the real world applications.</dd>
</div>
<div class="card">
<dt><a href="./callstats.html">Callstats</a></dt>
<dd>Visualize and compare runtime call stats.</dd>
......@@ -75,10 +79,6 @@ dd, dt {
<dt><a href="./ic-explorer.html">IC Explorer</a></dt>
<dd>Analyse inline caches.</dd>
</div>
<div class="card">
<dt><a href="./map-processor.html">Map Processor</a></dt>
<dd>Analyse Maps and their transition trees.</dd>
</div>
<div class="card">
<dt><a href="./parse-processor.html">Parse Processor</a></dt>
<dd>Analyse parse, compile and first-execution.</dd>
......@@ -87,10 +87,6 @@ dd, dt {
<dt><a href="./profview/index.html">Profview</a></dt>
<dd>Fancy sampling profile viewer.</dd>
</div>
<div class="card">
<dt><a href="./system-analyzer/index.html">System Analyzer</a></dt>
<dd>A unified web interface to trace, debug and analyse patterns of how Maps/ICs are created in the real world applications.</dd>
</div>
<div class="card">
<dt><a href="./tick-processor.html">Tick Processor</a></dt>
<dd>Simple sampling profile viewer.</dd>
......
#!/bin/sh
# find the name of the log file to process, it must not start with a dash.
log_file="v8.log"
for arg in "$@"
do
if ! expr "X${arg}" : "^X-" > /dev/null; then
log_file=${arg}
fi
done
tools_path=`cd $(dirname "$0");pwd`
if [ ! "$D8_PATH" ]; then
d8_public=`which d8`
if [ -x "$d8_public" ]; then D8_PATH=$(dirname "$d8_public"); fi
fi
[ -n "$D8_PATH" ] || D8_PATH=$tools_path/..
d8_exec=$D8_PATH/d8
if [ ! -x "$d8_exec" ]; then
D8_PATH=`pwd`/out/native
d8_exec=$D8_PATH/d8
fi
if [ ! -x "$d8_exec" ]; then
d8_exec=`grep -m 1 -o '".*/d8"' $log_file | sed 's/"//g'`
fi
if [ ! -x "$d8_exec" ]; then
echo "d8 shell not found in $D8_PATH"
echo "Please provide path to d8 as env var in D8_PATH"
exit 1
fi
# nm spits out 'no symbols found' messages to stderr.
cat $log_file | $d8_exec \
--module $tools_path/map-processor-driver.mjs -- $@ 2>/dev/null
// Copyright 2017 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 { WebInspector } from "./sourcemap.mjs";
import {
MapProcessor, ArgumentsProcessor, readFile
} from "./map-processor.mjs";
function processArguments(args) {
var processor = new ArgumentsProcessor(args);
if (processor.parse()) {
return processor.result();
} else {
processor.printUsageAndExit();
}
}
function initSourceMapSupport() {
// Pull dev tools source maps into our name space.
SourceMap = WebInspector.SourceMap;
// Overwrite the load function to load scripts synchronously.
SourceMap.load = function(sourceMapURL) {
var content = readFile(sourceMapURL);
var sourceMapObject = (JSON.parse(content));
return new SourceMap(sourceMapURL, sourceMapObject);
};
}
var params = processArguments(arguments);
var sourceMap = null;
if (params.sourceMap) {
initSourceMapSupport();
sourceMap = SourceMap.load(params.sourceMap);
}
var mapProcessor = new MapProcessor();
mapProcessor.processLogFile(params.logFileName);
<!DOCTYPE html>
<html>
<!--
Copyright 2017 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>
<meta charset="utf-8">
<style>
html, body {
font-family: sans-serif;
padding: 0px;
margin: 0px;
}
h1, h2, h3, section {
padding-left: 15px;
}
kbd {
background-color: #eee;
border-radius: 3px;
border: 1px solid black;
display: inline-block;
font-size: .9em;
font-weight: bold;
padding: 0px 4px 2px 4px;
white-space: nowrap;
}
dl {
display: grid;
grid-template-columns: min-content auto;
grid-gap: 10px;
}
dt {
text-align: right;
white-space: nowrap;
}
dd {
margin: 0;
}
#content {
opacity: 0.0;
height: 0px;
transition: all 0.5s ease-in-out;
}
.success #content {
height: auto;
opacity: 1.0;
}
#fileReader {
width: 100%;
height: 100px;
line-height: 100px;
text-align: center;
border: solid 1px #000000;
border-radius: 5px;
cursor: pointer;
transition: all 0.5s ease-in-out;
}
.failure #fileReader {
background-color: #FFAAAA;
}
.success #fileReader {
height: 20px;
line-height: 20px;
}
#fileReader:hover {
background-color: #e0edfe;
}
.loading #fileReader {
cursor: wait;
}
#fileReader > input {
display: none;
}
#loader {
display: none;
}
.loading #loader {
display: block;
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.5);
}
#spinner {
position: absolute;
width: 100px;
height: 100px;
top: 40%;
left: 50%;
margin-left: -50px;
border: 30px solid #000;
border-top: 30px solid #36E;
border-radius: 50%;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.colorbox {
width: 10px;
height: 10px;
border: 1px black solid;
}
#stats {
display: flex;
height: 250px;
}
#stats table {
flex: 1;
padding-right: 50px;
max-height: 250px;
display: inline-block;
}
#stats table td {
cursor: pointer;
}
#stats .transitionTable {
overflow-y: scroll;
}
#stats .transitionTable tr {
max-width: 200px;
}
#stats .transitionType {
text-align: right;
max-width: 380px;
}
#stats .transitionType tr td:nth-child(2) {
text-align: left;
}
#stats table thead td {
border-bottom: 1px black dotted;
}
#timeline {
position: relative;
height: 300px;
overflow-y: hidden;
overflow-x: scroll;
user-select: none;
}
#timelineLabel {
transform: rotate(90deg);
transform-origin: left bottom 0;
position: absolute;
left: 0;
width: 250px;
text-align: center;
font-size: 10px;
opacity: 0.5;
}
#timelineChunks {
height: 250px;
position: absolute;
margin-right: 100px;
}
#timelineCanvas {
height: 250px;
position: relative;
overflow: visible;
pointer-events: none;
}
.chunk {
width: 6px;
border: 0px white solid;
border-width: 0 2px 0 2px;
position: absolute;
background-size: 100% 100%;
image-rendering: pixelated;
bottom: 0px;
}
.timestamp {
height: 250px;
width: 100px;
border-left: 1px black dashed;
padding-left: 4px;
position: absolute;
pointer-events: none;
font-size: 10px;
opacity: 0.5;
}
#timelineOverview {
width: 100%;
height: 50px;
position: relative;
margin-top: -50px;
margin-bottom: 10px;
background-size: 100% 100%;
border: 1px black solid;
border-width: 1px 0 1px 0;
overflow: hidden;
}
#timelineOverviewIndicator {
height: 100%;
position: absolute;
box-shadow: 0px 2px 20px -5px black inset;
top: 0px;
cursor: ew-resize;
}
#timelineOverviewIndicator .leftMask,
#timelineOverviewIndicator .rightMask {
background-color: rgba(200, 200, 200, 0.5);
width: 10000px;
height: 100%;
position: absolute;
top: 0px;
}
#timelineOverviewIndicator .leftMask {
right: 100%;
}
#timelineOverviewIndicator .rightMask {
left: 100%;
}
#mapDetails {
font-family: monospace;
white-space: pre;
}
#transitionView {
overflow-x: scroll;
white-space: nowrap;
min-height: 50px;
max-height: 200px;
padding: 50px 0 0 0;
margin-top: -25px;
width: 100%;
}
.map {
width: 20px;
height: 20px;
display: inline-block;
border-radius: 50%;
background-color: black;
border: 4px solid white;
font-size: 10px;
text-align: center;
line-height: 18px;
color: white;
vertical-align: top;
margin-top: -13px;
/* raise z-index */
position: relative;
z-index: 2;
cursor: pointer;
}
.map.selected {
border-color: black;
}
.transitions {
display: inline-block;
margin-left: -15px;
}
.transition {
min-height: 55px;
margin: 0 0 -2px 2px;
}
/* gray out deprecated transitions */
.deprecated > .transitionEdge,
.deprecated > .map {
opacity: 0.5;
}
.deprecated > .transition {
border-color: rgba(0, 0, 0, 0.5);
}
/* Show a border for all but the first transition */
.transition:nth-of-type(2),
.transition:nth-last-of-type(n+2) {
border-left: 2px solid;
margin-left: 0px;
}
/* special case for 2 transitions */
.transition:nth-last-of-type(1) {
border-left: none;
}
/* topmost transitions are not related */
#transitionView > .transition {
border-left: none;
}
/* topmost transition edge needs initial offset to be aligned */
#transitionView > .transition > .transitionEdge {
margin-left: 13px;
}
.transitionEdge {
height: 2px;
width: 80px;
display: inline-block;
margin: 0 0 2px 0;
background-color: black;
vertical-align: top;
padding-left: 15px;
}
.transitionLabel {
color: black;
transform: rotate(-15deg);
transform-origin: top left;
margin-top: -10px;
font-size: 10px;
white-space: normal;
word-break: break-all;
background-color: rgba(255,255,255,0.5);
}
.black{
background-color: black;
}
.red {
background-color: red;
}
.green {
background-color: green;
}
.yellow {
background-color: yellow;
color: black;
}
.blue {
background-color: blue;
}
.orange {
background-color: orange;
}
.violet {
background-color: violet;
color: black;
}
.showSubtransitions {
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 10px solid black;
cursor: zoom-in;
margin: 4px 0 0 4px;
}
.showSubtransitions.opened {
border-top: none;
border-bottom: 10px solid black;
cursor: zoom-out;
}
#tooltip {
position: absolute;
width: 10px;
height: 10px;
background-color: red;
pointer-events: none;
z-index: 100;
display: none;
}
#searchBarInput {
width: 200px;
}
</style>
<script type="module" src="./map-processor.js"></script>
<script>
"use strict"
// =========================================================================
const kChunkHeight = 250;
const kChunkWidth = 10;
class State {
constructor() {
this._nofChunks = 400;
this._map = undefined;
this._timeline = undefined;
this._chunks = undefined;
this._view = new View(this);
this._navigation = new Navigation(this, this.view);
}
get timeline() { return this._timeline }
set timeline(value) {
this._timeline = value;
this.updateChunks();
this.view.updateTimeline();
this.view.updateStats();
}
get chunks() { return this._chunks }
get nofChunks() { return this._nofChunks }
set nofChunks(count) {
this._nofChunks = count;
this.updateChunks();
this.view.updateTimeline();
}
get view() { return this._view }
get navigation() { return this._navigation }
get map() { return this._map }
set map(value) {
this._map = value;
this._navigation.updateUrl();
this.view.updateMapDetails();
this.view.redraw();
}
updateChunks() {
this._chunks = this._timeline.chunks(this._nofChunks);
}
get entries() {
if (!this.map) return {};
return {
map: this.map.id,
time: this.map.time
}
}
}
// =========================================================================
// DOM Helper
function $(id) {
return document.getElementById(id)
}
function removeAllChildren(node) {
while (node.lastChild) {
node.removeChild(node.lastChild);
}
}
function selectOption(select, match) {
let options = select.options;
for (let i = 0; i < options.length; i++) {
if (match(i, options[i])) {
select.selectedIndex = i;
return;
}
}
}
function div(classes) {
let node = document.createElement('div');
if (classes !== void 0) {
if (typeof classes === "string") {
node.classList.add(classes);
} else {
classes.forEach(cls => node.classList.add(cls));
}
}
return node;
}
function table(className) {
let node = document.createElement("table")
if (className) node.classList.add(className)
return node;
}
function td(textOrNode) {
let node = document.createElement("td");
if (typeof textOrNode === "object") {
node.appendChild(textOrNode);
} else {
node.innerText = textOrNode;
}
return node;
}
function tr() {
return document.createElement("tr");
}
define(Array.prototype, "histogram", function(mapFn) {
let histogram = [];
for (let i = 0; i < this.length; i++) {
let value = this[i];
let index = Math.round(mapFn(value))
let bucket = histogram[index];
if (bucket !== undefined) {
bucket.push(value);
} else {
histogram[index] = [value];
}
}
for (let i = 0; i < histogram.length; i++) {
histogram[i] = histogram[i] || [];
}
return histogram;
});
// =========================================================================
// EventHandlers
function handleSearchBar(){
let searchBar = $('searchBarInput');
let searchBarInput = searchBar.value;
let selectedMap = V8Map.get(searchBarInput);
//removeAllChildren($('mapIdList'));
if(selectedMap){
let map = selectedMap;
document.state.map = map;
searchBar.className = "green";
} else {
searchBar.className = "red";
}
}
function handleBodyLoad() {
let upload = $('fileReader');
upload.onclick = (e) => $("file").click();
upload.ondragover = (e) => e.preventDefault();
upload.ondrop = (e) => handleLoadFile(e);
$('file').onchange = (e) => handleLoadFile(e);
upload.onkeydown = (e) => {
if (event.key == "Enter") $("file").click();
};
upload.focus();
document.state = new State();
$("transitionView").addEventListener("mousemove", e => {
let tooltip = $("tooltip");
tooltip.style.left = e.pageX + "px";
tooltip.style.top = e.pageY + "px";
let map = e.target.map;
if (map) {
$("tooltipContents").innerText = map.description;
}
});
function handleLoadFile(event) {
// Used for drop and file change.
event.preventDefault();
let host = event.dataTransfer ? event.dataTransfer : event.target;
let file = host.files[0];
let reader = new FileReader();
document.body.className = 'loading';
reader.onload = function(evt) {
try {
handleLoadText(this.result);
document.body.className = 'success';
} catch(e) {
document.body.className = 'failure';
console.error(e);
}
}
// Defer the reading to allow spinner CSS animation.
setTimeout(() => reader.readAsText(file), 0);
}
}
function handleLoadText(text) {
let mapProcessor = new MapProcessor();
document.state.timeline = mapProcessor.processString(text);
}
function handleKeyDown(event) {
let nav = document.state.navigation;
switch(event.key) {
case "ArrowUp":
event.preventDefault();
if (event.shiftKey) {
nav.selectPrevEdge();
} else {
nav.moveInChunk(-1);
}
return false;
case "ArrowDown":
event.preventDefault();
if (event.shiftKey) {
nav.selectNextEdge();
} else {
nav.moveInChunk(1);
}
return false;
case "ArrowLeft":
nav.moveInChunks(false);
break;
case "ArrowRight":
nav.moveInChunks(true);
break;
case "+":
nav.increaseTimelineResolution();
break;
case "-":
nav.decreaseTimelineResolution();
break;
}
};
document.onkeydown = handleKeyDown;
function handleTimelineIndicatorMove(event) {
if (event.buttons == 0) return;
let timelineTotalWidth = $("timelineCanvas").offsetWidth;
let factor = $("timelineOverview").offsetWidth / timelineTotalWidth;
$("timeline").scrollLeft += event.movementX / factor;
}
// =========================================================================
Object.defineProperty(Edge.prototype, 'getColor', { value:function() {
return transitionTypeToColor(this.type);
}});
class Navigation {
constructor(state, view) {
this.state = state;
this.view = view;
}
get map() { return this.state.map }
set map(value) { this.state.map = value }
get chunks() { return this.state.chunks }
increaseTimelineResolution() {
this.state.nofChunks *= 1.5;
}
decreaseTimelineResolution() {
this.state.nofChunks /= 1.5;
}
selectNextEdge() {
if (!this.map) return;
if (this.map.children.length != 1) return;
this.map = this.map.children[0].to;
}
selectPrevEdge() {
if (!this.map) return;
if (!this.map.parent()) return;
this.map = this.map.parent();
}
selectDefaultMap() {
this.map = this.chunks[0].at(0);
}
moveInChunks(next) {
if (!this.map) return this.selectDefaultMap();
let chunkIndex = this.map.chunkIndex(this.chunks);
let chunk = this.chunks[chunkIndex];
let index = chunk.indexOf(this.map);
if (next) {
chunk = chunk.next(this.chunks);
} else {
chunk = chunk.prev(this.chunks);
}
if (!chunk) return;
index = Math.min(index, chunk.size()-1);
this.map = chunk.at(index);
}
moveInChunk(delta) {
if (!this.map) return this.selectDefaultMap();
let chunkIndex = this.map.chunkIndex(this.chunks)
let chunk = this.chunks[chunkIndex];
let index = chunk.indexOf(this.map) + delta;
let map;
if (index < 0) {
map = chunk.prev(this.chunks).last();
} else if (index >= chunk.size()) {
map = chunk.next(this.chunks).first()
} else {
map = chunk.at(index);
}
this.map = map;
}
updateUrl() {
let entries = this.state.entries;
let params = new URLSearchParams(entries);
window.history.pushState(entries, "", "?" + params.toString());
}
}
class View {
constructor(state) {
this.state = state;
setInterval(this.updateOverviewWindow, 50);
this.backgroundCanvas = document.createElement("canvas");
this.transitionView = new TransitionView(state, $("transitionView"));
this.statsView = new StatsView(state, $("stats"));
this.isLocked = false;
}
get chunks() { return this.state.chunks }
get timeline() { return this.state.timeline }
get map() { return this.state.map }
updateStats() {
this.statsView.update();
}
updateMapDetails() {
let details = "";
if (this.map) {
details += "ID: " + this.map.id;
details += "\nSource location: " + this.map.filePosition;
details += "\n" + this.map.description;
}
$("mapDetails").innerText = details;
this.transitionView.showMap(this.map);
}
updateTimeline() {
let chunksNode = $("timelineChunks");
removeAllChildren(chunksNode);
let chunks = this.chunks;
let max = chunks.max(each => each.size());
let start = this.timeline.startTime;
let end = this.timeline.endTime;
let duration = end - start;
const timeToPixel = chunks.length * kChunkWidth / duration;
let addTimestamp = (time, name) => {
let timeNode = div("timestamp");
timeNode.innerText = name;
timeNode.style.left = ((time-start) * timeToPixel) + "px";
chunksNode.appendChild(timeNode);
};
let backgroundTodo = [];
for (let i = 0; i < chunks.length; i++) {
let chunk = chunks[i];
let height = (chunk.size() / max * kChunkHeight);
chunk.height = height;
if (chunk.isEmpty()) continue;
let node = div();
node.className = "chunk";
node.style.left = (i * kChunkWidth) + "px";
node.style.height = height + "px";
node.chunk = chunk;
node.addEventListener("mousemove", e => this.handleChunkMouseMove(e));
node.addEventListener("click", e => this.handleChunkClick(e));
node.addEventListener("dblclick", e => this.handleChunkDoubleClick(e));
backgroundTodo.push([chunk, node])
chunksNode.appendChild(node);
chunk.markers.forEach(marker => addTimestamp(marker.time, marker.name));
}
this.asyncSetTimelineChunkBackground(backgroundTodo)
// Put a time marker roughly every 20 chunks.
let expected = duration / chunks.length * 20;
let interval = (10 ** Math.floor(Math.log10(expected)));
let correction = Math.log10(expected / interval);
correction = (correction < 0.33) ? 1 : (correction < 0.75) ? 2.5 : 5;
interval *= correction;
let time = start;
while (time < end) {
addTimestamp(time, ((time-start) / 1000) + " ms");
time += interval;
}
this.drawOverview();
this.redraw();
}
handleChunkMouseMove(event) {
if (this.isLocked) return false;
let chunk = event.target.chunk;
if (!chunk) return;
// topmost map (at chunk.height) == map #0.
let relativeIndex =
Math.round(event.layerY / event.target.offsetHeight * chunk.size());
let map = chunk.at(relativeIndex);
this.state.map = map;
}
handleChunkClick(event) {
this.isLocked = !this.isLocked;
}
handleChunkDoubleClick(event) {
this.isLocked = true;
let chunk = event.target.chunk;
if (!chunk) return;
this.transitionView.showMaps(chunk.getUniqueTransitions());
}
asyncSetTimelineChunkBackground(backgroundTodo) {
const kIncrement = 100;
let start = 0;
let delay = 1;
while (start < backgroundTodo.length) {
let end = Math.min(start+kIncrement, backgroundTodo.length);
setTimeout((from, to) => {
for (let i = from; i < to; i++) {
let [chunk, node] = backgroundTodo[i];
this.setTimelineChunkBackground(chunk, node);
}
}, delay++, start, end);
start = end;
}
}
setTimelineChunkBackground(chunk, node) {
// Render the types of transitions as bar charts
const kHeight = chunk.height;
const kWidth = 1;
this.backgroundCanvas.width = kWidth;
this.backgroundCanvas.height = kHeight;
let ctx = this.backgroundCanvas.getContext("2d");
ctx.clearRect(0, 0, kWidth, kHeight);
let y = 0;
let total = chunk.size();
let type, count;
if (true) {
chunk.getTransitionBreakdown().forEach(([type, count]) => {
ctx.fillStyle = transitionTypeToColor(type);
let height = count / total * kHeight;
ctx.fillRect(0, y, kWidth, y + height);
y += height;
});
} else {
chunk.items.forEach(map => {
ctx.fillStyle = transitionTypeToColor(map.getType());
let y = chunk.yOffset(map);
ctx.fillRect(0, y, kWidth, y + 1);
});
}
let imageData = this.backgroundCanvas.toDataURL("image/webp", 0.2);
node.style.backgroundImage = "url(" + imageData + ")";
}
updateOverviewWindow() {
let indicator = $("timelineOverviewIndicator");
let totalIndicatorWidth = $("timelineOverview").offsetWidth;
let div = $("timeline");
let timelineTotalWidth = $("timelineCanvas").offsetWidth;
let factor = $("timelineOverview").offsetWidth / timelineTotalWidth;
let width = div.offsetWidth * factor;
let left = div.scrollLeft * factor;
indicator.style.width = width + "px";
indicator.style.left = left + "px";
}
drawOverview() {
const height = 50;
const kFactor = 2;
let canvas = this.backgroundCanvas;
canvas.height = height;
canvas.width = window.innerWidth;
let ctx = canvas.getContext("2d");
let chunks = this.state.timeline.chunkSizes(canvas.width * kFactor);
let max = chunks.max();
ctx.clearRect(0, 0, canvas.width, height);
ctx.strokeStyle = "black";
ctx.fillStyle = "black";
ctx.beginPath();
ctx.moveTo(0,height);
for (let i = 0; i < chunks.length; i++) {
ctx.lineTo(i/kFactor, height - chunks[i]/max * height);
}
ctx.lineTo(chunks.length, height);
ctx.stroke();
ctx.closePath();
ctx.fill();
let imageData = canvas.toDataURL("image/webp", 0.2);
$("timelineOverview").style.backgroundImage = "url(" + imageData + ")";
}
redraw() {
let canvas= $("timelineCanvas");
canvas.width = (this.chunks.length+1) * kChunkWidth;
canvas.height = kChunkHeight;
let ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, kChunkHeight);
if (!this.state.map) return;
this.drawEdges(ctx);
}
setMapStyle(map, ctx) {
ctx.fillStyle = map.edge && map.edge.from ? "black" : "green";
}
setEdgeStyle(edge, ctx) {
let color = edge.getColor();
ctx.strokeStyle = color;
ctx.fillStyle = color;
}
markMap(ctx, map) {
let [x, y] = map.position(this.state.chunks);
ctx.beginPath();
this.setMapStyle(map, ctx);
ctx.arc(x, y, 3, 0, 2 * Math.PI);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "white";
ctx.arc(x, y, 2, 0, 2 * Math.PI);
ctx.fill();
}
markSelectedMap(ctx, map) {
let [x, y] = map.position(this.state.chunks);
ctx.beginPath();
this.setMapStyle(map, ctx);
ctx.arc(x, y, 6, 0, 2 * Math.PI);
ctx.stroke();
}
drawEdges(ctx) {
// Draw the trace of maps in reverse order to make sure the outgoing
// transitions of previous maps aren't drawn over.
const kMaxOutgoingEdges = 100;
let nofEdges = 0;
let stack = [];
let current = this.state.map;
while (current && nofEdges < kMaxOutgoingEdges) {
nofEdges += current.children.length;
stack.push(current);
current = current.parent();
}
ctx.save();
this.drawOutgoingEdges(ctx, this.state.map, 3);
ctx.restore();
let labelOffset = 15;
let xPrev = 0;
while (current = stack.pop()) {
if (current.edge) {
this.setEdgeStyle(current.edge, ctx);
let [xTo, yTo] = this.drawEdge(ctx, current.edge, true, labelOffset);
if (xTo == xPrev) {
labelOffset += 8;
} else {
labelOffset = 15
}
xPrev = xTo;
}
this.markMap(ctx, current);
current = current.parent();
ctx.save();
// this.drawOutgoingEdges(ctx, current, 1);
ctx.restore();
}
// Mark selected map
this.markSelectedMap(ctx, this.state.map);
}
drawEdge(ctx, edge, showLabel=true, labelOffset=20) {
if (!edge.from || !edge.to) return [-1, -1];
let [xFrom, yFrom] = edge.from.position(this.chunks);
let [xTo, yTo] = edge.to.position(this.chunks);
let sameChunk = xTo == xFrom;
if (sameChunk) labelOffset += 8;
ctx.beginPath();
ctx.moveTo(xFrom, yFrom);
let offsetX = 20;
let offsetY = 20;
let midX = xFrom + (xTo- xFrom) / 2;
let midY = (yFrom + yTo) / 2 - 100;
if (!sameChunk) {
ctx.quadraticCurveTo(midX, midY, xTo, yTo);
} else {
ctx.lineTo(xTo, yTo);
}
if (!showLabel) {
ctx.stroke();
} else {
let centerX, centerY;
if (!sameChunk) {
centerX = (xFrom/2 + midX + xTo/2)/2;
centerY = (yFrom/2 + midY + yTo/2)/2;
} else {
centerX = xTo;
centerY = yTo;
}
ctx.moveTo(centerX, centerY);
ctx.lineTo(centerX + offsetX, centerY - labelOffset);
ctx.stroke();
ctx.textAlign = "left";
ctx.fillText(edge.toString(), centerX + offsetX + 2, centerY - labelOffset)
}
return [xTo, yTo];
}
drawOutgoingEdges(ctx, map, max=10, depth=0) {
if (!map) return;
if (depth >= max) return;
ctx.globalAlpha = 0.5 - depth * (0.3/max);
ctx.strokeStyle = "#666";
const limit = Math.min(map.children.length, 100)
for (let i = 0; i < limit; i++) {
let edge = map.children[i];
this.drawEdge(ctx, edge, true);
this.drawOutgoingEdges(ctx, edge.to, max, depth+1);
}
}
}
class TransitionView {
constructor(state, node) {
this.state = state;
this.container = node;
this.currentNode = node;
this.currentMap = undefined;
}
selectMap(map) {
this.currentMap = map;
this.state.map = map;
}
showMap(map) {
if (this.currentMap === map) return;
this.currentMap = map;
this._showMaps([map]);
}
showMaps(list, name) {
this.state.view.isLocked = true;
this._showMaps(list);
}
_showMaps(list, name) {
// Hide the container to avoid any layouts.
this.container.style.display = "none";
removeAllChildren(this.container);
list.forEach(map => this.addMapAndParentTransitions(map));
this.container.style.display = ""
}
addMapAndParentTransitions(map) {
if (map === void 0) return;
this.currentNode = this.container;
let parents = map.getParents();
if (parents.length > 0) {
this.addTransitionTo(parents.pop());
parents.reverse().forEach(each => this.addTransitionTo(each));
}
let mapNode = this.addSubtransitions(map);
// Mark and show the selected map.
mapNode.classList.add("selected");
if (this.selectedMap == map) {
setTimeout(() => mapNode.scrollIntoView({
behavior: "smooth", block: "nearest", inline: "nearest"
}), 1);
}
}
addMapNode(map) {
let node = div("map");
if (map.edge) node.classList.add(map.edge.getColor());
node.map = map;
node.addEventListener("click", () => this.selectMap(map));
if (map.children.length > 1) {
node.innerText = map.children.length;
let showSubtree = div("showSubtransitions");
showSubtree.addEventListener("click", (e) => this.toggleSubtree(e, node));
node.appendChild(showSubtree);
} else if (map.children.length == 0) {
node.innerHTML = "&#x25CF;"
}
this.currentNode.appendChild(node);
return node;
}
addSubtransitions(map) {
let mapNode = this.addTransitionTo(map);
// Draw outgoing linear transition line.
let current = map;
while (current.children.length == 1) {
current = current.children[0].to;
this.addTransitionTo(current);
}
return mapNode;
}
addTransitionEdge(map) {
let classes = ["transitionEdge", map.edge.getColor()];
let edge = div(classes);
let labelNode = div("transitionLabel");
labelNode.innerText = map.edge.toString();
edge.appendChild(labelNode);
return edge;
}
addTransitionTo(map) {
// transition[ transitions[ transition[...], transition[...], ...]];
let transition = div("transition");
if (map.isDeprecated()) transition.classList.add("deprecated");
if (map.edge) {
transition.appendChild(this.addTransitionEdge(map));
}
let mapNode = this.addMapNode(map);
transition.appendChild(mapNode);
let subtree = div("transitions");
transition.appendChild(subtree);
this.currentNode.appendChild(transition);
this.currentNode = subtree;
return mapNode;
}
toggleSubtree(event, node) {
let map = node.map;
event.target.classList.toggle("opened");
let transitionsNode = node.parentElement.querySelector(".transitions");
let subtransitionNodes = transitionsNode.children;
if (subtransitionNodes.length <= 1) {
// Add subtransitions excepth the one that's already shown.
let visibleTransitionMap = subtransitionNodes.length == 1 ?
transitionsNode.querySelector(".map").map : void 0;
map.children.forEach(edge => {
if (edge.to != visibleTransitionMap) {
this.currentNode = transitionsNode;
this.addSubtransitions(edge.to);
}
});
} else {
// remove all but the first (currently selected) subtransition
for (let i = subtransitionNodes.length-1; i > 0; i--) {
transitionsNode.removeChild(subtransitionNodes[i]);
}
}
}
}
class StatsView {
constructor(state, node) {
this.state = state;
this.node = node;
}
get timeline() { return this.state.timeline }
get transitionView() { return this.state.view.transitionView; }
update() {
removeAllChildren(this.node);
this.updateGeneralStats();
this.updateNamedTransitionsStats();
}
updateGeneralStats() {
let pairs = [
["Total", null, e => true],
["Transitions", 'black', e => e.edge && e.edge.isTransition()],
["Fast to Slow", 'violet', e => e.edge && e.edge.isFastToSlow()],
["Slow to Fast", 'orange', e => e.edge && e.edge.isSlowToFast()],
["Initial Map", 'yellow', e => e.edge && e.edge.isInitial()],
["Replace Descriptors", 'red', e => e.edge && e.edge.isReplaceDescriptors()],
["Copy as Prototype", 'red', e => e.edge && e.edge.isCopyAsPrototype()],
["Optimize as Prototype", null, e => e.edge && e.edge.isOptimizeAsPrototype()],
["Deprecated", null, e => e.isDeprecated()],
["Bootstrapped", 'green', e => e.isBootstrapped()],
];
let text = "";
let tableNode = table("transitionType");
tableNode.innerHTML = "<thead><tr><td>Color</td><td>Type</td><td>Count</td><td>Percent</td></tr></thead>";
let name, filter;
let total = this.timeline.size();
pairs.forEach(([name, color, filter]) => {
let row = tr();
if (color !== null) {
row.appendChild(td(div(['colorbox', color])));
} else {
row.appendChild(td(""));
}
row.onclick = (e) => {
// lazily compute the stats
let node = e.target.parentNode;
if (node.maps == undefined) {
node.maps = this.timeline.filterUniqueTransitions(filter);
}
this.transitionView.showMaps(node.maps);
}
row.appendChild(td(name));
let count = this.timeline.count(filter);
row.appendChild(td(count));
let percent = Math.round(count / total * 1000) / 10;
row.appendChild(td(percent.toFixed(1) + "%"));
tableNode.appendChild(row);
});
this.node.appendChild(tableNode);
};
updateNamedTransitionsStats() {
let tableNode = table("transitionTable");
let nameMapPairs = Array.from(this.timeline.transitions.entries());
tableNode.innerHTML = "<thead><tr><td>Propery Name</td><td>#</td></tr></thead>";
nameMapPairs
.sort((a,b) => b[1].length - a[1].length)
.forEach(([name, maps]) => {
let row = tr();
row.maps = maps;
row.addEventListener("click",
e => this.transitionView.showMaps(
e.target.parentNode.maps.map(map => map.to)));
row.appendChild(td(name));
row.appendChild(td(maps.length));
tableNode.appendChild(row);
});
this.node.appendChild(tableNode);
}
}
// =========================================================================
function transitionTypeToColor(type) {
switch(type) {
case "new": return "green";
case "Normalize": return "violet";
case "SlowToFast": return "orange";
case "InitialMap": return "yellow";
case "Transition": return "black";
case "ReplaceDescriptors": return "red";
}
return "black";
}
// ShadowDom elements =========================================================
</script>
</head>
<body onload="handleBodyLoad(event)" onkeypress="handleKeyDown(event)">
<h1>V8 Map Explorer</h1>
<section>
<div id="fileReader" tabindex=1 >
<span id="label">
Drag and drop a v8.log file into this area, or click to choose from disk.
</span>
<input id="file" type="file" name="files">
</div>
<div id="loader">
<div id="spinner"></div>
</div>
</section>
<div id="content">
<h2>Stats</h2>
<section id="stats"></section>
<h2>Timeline</h2>
<div id="timeline">
<div id="timelineLabel">Frequency</div>
<div id="timelineChunks"></div>
<canvas id="timelineCanvas"></canvas>
</div>
<div id="timelineOverview"
onmousemove="handleTimelineIndicatorMove(event)" >
<div id="timelineOverviewIndicator">
<div class="leftMask"></div>
<div class="rightMask"></div>
</div>
</div>
<h2>Transitions</h2>
<section id="transitionView"></section>
<br/>
<h2>Search Map by Address</h2>
<section id="searchBar"></section>
<input type="search" id="searchBarInput" placeholder="Search maps by address..">
<button onclick="handleSearchBar()">Search</button>
<ul id="mapIdList" title="Map Id List">
</ul>
<h2>Selected Map</h2>
<section id="mapDetails"></section>
</div>
<section>
<h2>Instructions</h2>
<p>Visualize Map trees that have been gathered using <code>path/to/d8 $FILE --trace-maps</code>.</p>
<p>You can inspect the transition tree in DevTools by looking at <code>document.state.timeline.values</code>.
<h3>Keyboard Shortcuts</h3>
<dl>
<dt><kbd>SHIFT</kbd> + <kbd>Arrow Up</kbd></dt>
<dd>Follow Map transition forward (first child)</dd>
<dt><kbd>SHIFT</kbd> + <kbd>Arrow Down</kbd></dt>
<dd>Follow Map transition backwards</dd>
<dt><kbd>Arrow Up</kbd></dt>
<dd>Go to previous Map chunk</dd>
<dt><kbd>Arrow Down</kbd></dt>
<dd>Go to next Map in chunk</dd>
<dt><kbd>Arrow Left</kbd></dt>
<dd>Go to previous chunk</dd>
<dt><kbd>Arrow Right</kbd></dt>
<dd>Go to next chunk</dd>
<dt><kbd>+</kbd></dt>
<dd>Timeline zoom in</dd>
<dt><kbd>-</kbd></dt>
<dd>Timeline zoom out</dd>
</dl>
</section>
<div id="tooltip">
<div id="tooltipContents"></div>
</div>
</body>
</html>
// Copyright 2017 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 { LogReader, parseString, parseVarArgs } from "./logreader.mjs";
import { BaseArgumentsProcessor } from "./arguments.mjs";
import { Profile } from "./profile.mjs";
// ===========================================================================
function define(prototype, name, fn) {
Object.defineProperty(prototype, name, {value:fn, enumerable:false});
}
define(Array.prototype, "max", function(fn) {
if (this.length === 0) return undefined;
if (fn === undefined) fn = (each) => each;
let max = fn(this[0]);
for (let i = 1; i < this.length; i++) {
max = Math.max(max, fn(this[i]));
}
return max;
})
define(Array.prototype, "first", function() { return this[0] });
define(Array.prototype, "last", function() { return this[this.length - 1] });
/**
* A thin wrapper around shell's 'read' function showing a file name on error.
*/
export function readFile(fileName) {
try {
return read(fileName);
} catch (e) {
console.log(fileName + ': ' + (e.message || e));
throw e;
}
}
// ===========================================================================
export class MapProcessor extends LogReader {
constructor() {
super();
this.dispatchTable_ = {
__proto__:null,
'code-creation': {
parsers: [parseString, parseInt, parseInt, parseInt, parseInt,
parseString, parseVarArgs],
processor: this.processCodeCreation
},
'code-move': {
parsers: [parseInt, parseInt],
'sfi-move': {
parsers: [parseInt, parseInt],
processor: this.processCodeMove
},
'code-delete': {
parsers: [parseInt],
processor: this.processCodeDelete
},
processor: this.processFunctionMove
},
'map-create': {
parsers: [parseInt, parseString],
processor: this.processMapCreate
},
'map': {
parsers: [parseString, parseInt, parseString, parseString, parseInt, parseInt,
parseString, parseString, parseString
],
processor: this.processMap
},
'map-details': {
parsers: [parseInt, parseString, parseString],
processor: this.processMapDetails
}
};
this.profile_ = new Profile();
this.timeline_ = new Timeline();
this.formatPCRegexp_ = /(.*):[0-9]+:[0-9]+$/;
}
printError(str) {
console.error(str);
throw str
}
processString(string) {
let end = string.length;
let current = 0;
let next = 0;
let line;
let i = 0;
let entry;
try {
while (current < end) {
next = string.indexOf("\n", current);
if (next === -1) break;
i++;
line = string.substring(current, next);
current = next + 1;
this.processLogLine(line);
}
} catch(e) {
console.error("Error occurred during parsing, trying to continue: " + e);
}
return this.finalize();
}
processLogFile(fileName) {
this.collectEntries = true
this.lastLogFileName_ = fileName;
let i = 1;
let line;
try {
while (line = readline()) {
this.processLogLine(line);
i++;
}
} catch(e) {
console.error("Error occurred during parsing line " + i + ", trying to continue: " + e);
}
return this.finalize();
}
finalize() {
// TODO(cbruni): print stats;
this.timeline_.finalize();
return this.timeline_;
}
addEntry(entry) {
this.entries.push(entry);
}
/**
* Parser for dynamic code optimization state.
*/
parseState(s) {
switch (s) {
case "":
return Profile.CodeState.COMPILED;
case "~":
return Profile.CodeState.OPTIMIZABLE;
case "*":
return Profile.CodeState.OPTIMIZED;
}
throw new Error("unknown code state: " + s);
}
processCodeCreation(
type, kind, timestamp, start, size, name, maybe_func) {
if (maybe_func.length) {
let funcAddr = parseInt(maybe_func[0]);
let state = this.parseState(maybe_func[1]);
this.profile_.addFuncCode(type, name, timestamp, start, size, funcAddr, state);
} else {
this.profile_.addCode(type, name, timestamp, start, size);
}
}
processCodeMove(from, to) {
this.profile_.moveCode(from, to);
}
processCodeDelete(start) {
this.profile_.deleteCode(start);
}
processFunctionMove(from, to) {
this.profile_.moveFunc(from, to);
}
formatPC(pc, line, column) {
let entry = this.profile_.findEntry(pc);
if (!entry) return "<unknown>"
if (entry.type === "Builtin") {
return entry.name;
}
let name = entry.func.getName();
let array = this.formatPCRegexp_.exec(name);
if (array === null) {
entry = name;
} else {
entry = entry.getState() + array[1];
}
return entry + ":" + line + ":" + column;
}
processMap(type, time, from, to, pc, line, column, reason, name) {
let time_ = parseInt(time);
if (type === "Deprecate") return this.deprecateMap(type, time_, from);
let from_ = this.getExistingMap(from, time_);
let to_ = this.getExistingMap(to, time_);
let edge = new Edge(type, name, reason, time, from_, to_);
to_.filePosition = this.formatPC(pc, line, column);
edge.finishSetup();
}
deprecateMap(type, time, id) {
this.getExistingMap(id, time).deprecate();
}
processMapCreate(time, id) {
// map-create events might override existing maps if the addresses get
// recycled. Hence we do not check for existing maps.
let map = this.createMap(id, time);
}
processMapDetails(time, id, string) {
//TODO(cbruni): fix initial map logging.
let map = this.getExistingMap(id, time);
map.description = string;
}
createMap(id, time) {
let map = new V8Map(id, time);
this.timeline_.push(map);
return map;
}
getExistingMap(id, time) {
if (id === "0x000000000000") return undefined;
let map = V8Map.get(id, time);
if (map === undefined) {
console.error("No map details provided: id=" + id);
// Manually patch in a map to continue running.
return this.createMap(id, time);
};
return map;
}
}
// ===========================================================================
class V8Map {
constructor(id, time = -1) {
if (!id) throw "Invalid ID";
this.id = id;
this.time = time;
if (!(time > 0)) throw "Invalid time";
this.description = "";
this.edge = void 0;
this.children = [];
this.depth = 0;
this._isDeprecated = false;
this.deprecationTargets = null;
V8Map.set(id, this);
this.leftId = 0;
this.rightId = 0;
this.filePosition = "";
}
finalizeRootMap(id) {
let stack = [this];
while (stack.length > 0) {
let current = stack.pop();
if (current.leftId !== 0) {
console.error("Skipping potential parent loop between maps:", current)
continue;
}
current.finalize(id)
id += 1;
current.children.forEach(edge => stack.push(edge.to))
// TODO implement rightId
}
return id;
}
finalize(id) {
// Initialize preorder tree traversal Ids for fast subtree inclusion checks
if (id <= 0) throw "invalid id";
let currentId = id;
this.leftId = currentId
}
parent() {
if (this.edge === void 0) return void 0;
return this.edge.from;
}
isDeprecated() {
return this._isDeprecated;
}
deprecate() {
this._isDeprecated = true;
}
isRoot() {
return this.edge === void 0 || this.edge.from === void 0;
}
contains(map) {
return this.leftId < map.leftId && map.rightId < this.rightId;
}
addEdge(edge) {
this.children.push(edge);
}
chunkIndex(chunks) {
// Did anybody say O(n)?
for (let i = 0; i < chunks.length; i++) {
let chunk = chunks[i];
if (chunk.isEmpty()) continue;
if (chunk.last().time < this.time) continue;
return i;
}
return -1;
}
position(chunks) {
let index = this.chunkIndex(chunks);
let xFrom = (index + 0.5) * kChunkWidth;
let yFrom = kChunkHeight - chunks[index].yOffset(this);
return [xFrom, yFrom];
}
transitions() {
let transitions = Object.create(null);
let current = this;
while (current) {
let edge = current.edge;
if (edge && edge.isTransition()) {
transitions[edge.name] = edge;
}
current = current.parent()
}
return transitions;
}
getType() {
return this.edge === void 0 ? "new" : this.edge.type;
}
isBootstrapped() {
return this.edge === void 0;
}
getParents() {
let parents = [];
let current = this.parent();
while (current) {
parents.push(current);
current = current.parent();
}
return parents;
}
static get(id, time = undefined) {
let maps = this.cache.get(id);
if(maps){
for (let i = 0; i < maps.length; i++) {
//TODO: Implement time based map search
if(maps[i].time === time){
return maps[i];
}
}
// default return the latest
return maps[maps.length-1];
}
}
static set(id, map) {
if(this.cache.has(id)){
this.cache.get(id).push(map);
} else {
this.cache.set(id, [map]);
}
}
}
V8Map.cache = new Map();
// ===========================================================================
class Edge {
constructor(type, name, reason, time, from, to) {
this.type = type;
this.name = name;
this.reason = reason;
this.time = time;
this.from = from;
this.to = to;
}
finishSetup() {
let from = this.from
if (from) from.addEdge(this);
let to = this.to;
if (to === undefined) return;
to.edge = this;
if (from === undefined ) return;
if (to === from) throw "From and to must be distinct.";
if (to.time < from.time) {
console.error("invalid time order");
}
let newDepth = from.depth + 1;
if (to.depth > 0 && to.depth != newDepth) {
console.error("Depth has already been initialized");
}
to.depth = newDepth;
}
chunkIndex(chunks) {
// Did anybody say O(n)?
for (let i = 0; i < chunks.length; i++) {
let chunk = chunks[i];
if (chunk.isEmpty()) continue;
if (chunk.last().time < this.time) continue;
return i;
}
return -1;
}
parentEdge() {
if (!this.from) return undefined;
return this.from.edge;
}
chainLength() {
let length = 0;
let prev = this;
while (prev) {
prev = this.parent;
length++;
}
return length;
}
isTransition() {
return this.type === "Transition"
}
isFastToSlow() {
return this.type === "Normalize"
}
isSlowToFast() {
return this.type === "SlowToFast"
}
isInitial() {
return this.type === "InitialMap"
}
isBootstrapped() {
return this.type === "new"
}
isReplaceDescriptors() {
return this.type === "ReplaceDescriptors"
}
isCopyAsPrototype() {
return this.reason === "CopyAsPrototype"
}
isOptimizeAsPrototype() {
return this.reason === "OptimizeAsPrototype"
}
symbol() {
if (this.isTransition()) return "+";
if (this.isFastToSlow()) return "⊡";
if (this.isSlowToFast()) return "⊛";
if (this.isReplaceDescriptors()) {
if (this.name) return "+";
return "∥";
}
return "";
}
toString() {
let s = this.symbol();
if (this.isTransition()) return s + this.name;
if (this.isFastToSlow()) return s + this.reason;
if (this.isCopyAsPrototype()) return s + "Copy as Prototype";
if (this.isOptimizeAsPrototype()) {
return s + "Optimize as Prototype";
}
if (this.isReplaceDescriptors() && this.name) {
return this.type + " " + this.symbol() + this.name;
}
return this.type + " " + (this.reason ? this.reason : "") + " " +
(this.name ? this.name : "")
}
}
// ===========================================================================
class Marker {
constructor(time, name) {
this.time = parseInt(time);
this.name = name;
}
}
// ===========================================================================
class Timeline {
constructor() {
this.values = [];
this.transitions = new Map();
this.markers = [];
this.startTime = 0;
this.endTime = 0;
}
push(map) {
let time = map.time;
if (!this.isEmpty() && this.last().time > time) {
// Invalid insertion order, might happen without --single-process,
// finding insertion point.
let insertionPoint = this.find(time);
this.values.splice(insertionPoint, map);
} else {
this.values.push(map);
}
if (time > 0) {
this.endTime = Math.max(this.endTime, time);
if (this.startTime === 0) {
this.startTime = time;
} else {
this.startTime = Math.min(this.startTime, time);
}
}
}
addMarker(time, message) {
this.markers.push(new Marker(time, message));
}
finalize() {
let id = 0;
this.forEach(map => {
if (map.isRoot()) id = map.finalizeRootMap(id + 1);
if (map.edge && map.edge.name) {
let edge = map.edge;
let list = this.transitions.get(edge.name);
if (list === undefined) {
this.transitions.set(edge.name, [edge]);
} else {
list.push(edge);
}
}
});
this.markers.sort((a, b) => b.time - a.time);
}
at(index) {
return this.values[index]
}
isEmpty() {
return this.size() === 0
}
size() {
return this.values.length
}
first() {
return this.values.first()
}
last() {
return this.values.last()
}
duration() {
return this.last().time - this.first().time
}
forEachChunkSize(count, fn) {
const increment = this.duration() / count;
let currentTime = this.first().time + increment;
let index = 0;
for (let i = 0; i < count; i++) {
let nextIndex = this.find(currentTime, index);
let nextTime = currentTime + increment;
fn(index, nextIndex, currentTime, nextTime);
index = nextIndex
currentTime = nextTime;
}
}
chunkSizes(count) {
let chunks = [];
this.forEachChunkSize(count, (start, end) => chunks.push(end - start));
return chunks;
}
chunks(count) {
let chunks = [];
let emptyMarkers = [];
this.forEachChunkSize(count, (start, end, startTime, endTime) => {
let items = this.values.slice(start, end);
let markers = this.markersAt(startTime, endTime);
chunks.push(new Chunk(chunks.length, startTime, endTime, items, markers));
});
return chunks;
}
range(start, end) {
const first = this.find(start);
if (first < 0) return [];
const last = this.find(end, first);
return this.values.slice(first, last);
}
find(time, offset = 0) {
return this.basicFind(this.values, each => each.time - time, offset);
}
markersAt(startTime, endTime) {
let start = this.basicFind(this.markers, each => each.time - startTime);
let end = this.basicFind(this.markers, each => each.time - endTime, start);
return this.markers.slice(start, end);
}
basicFind(array, cmp, offset = 0) {
let min = offset;
let max = array.length;
while (min < max) {
let mid = min + Math.floor((max - min) / 2);
let result = cmp(array[mid]);
if (result > 0) {
max = mid - 1;
} else {
min = mid + 1;
}
}
return min;
}
count(filter) {
return this.values.reduce((sum, each) => {
return sum + (filter(each) === true ? 1 : 0);
}, 0);
}
filter(predicate) {
return this.values.filter(predicate);
}
filterUniqueTransitions(filter) {
// Returns a list of Maps whose parent is not in the list.
return this.values.filter(map => {
if (filter(map) === false) return false;
let parent = map.parent();
if (parent === undefined) return true;
return filter(parent) === false;
});
}
depthHistogram() {
return this.values.histogram(each => each.depth);
}
fanOutHistogram() {
return this.values.histogram(each => each.children.length);
}
forEach(fn) {
return this.values.forEach(fn)
}
}
// ===========================================================================
class Chunk {
constructor(index, start, end, items, markers) {
this.index = index;
this.start = start;
this.end = end;
this.items = items;
this.markers = markers
this.height = 0;
}
isEmpty() {
return this.items.length === 0;
}
last() {
return this.at(this.size() - 1);
}
first() {
return this.at(0);
}
at(index) {
return this.items[index];
}
size() {
return this.items.length;
}
yOffset(map) {
// items[0] == oldest map, displayed at the top of the chunk
// items[n-1] == youngest map, displayed at the bottom of the chunk
return (1 - (this.indexOf(map) + 0.5) / this.size()) * this.height;
}
indexOf(map) {
return this.items.indexOf(map);
}
has(map) {
if (this.isEmpty()) return false;
return this.first().time <= map.time && map.time <= this.last().time;
}
next(chunks) {
return this.findChunk(chunks, 1);
}
prev(chunks) {
return this.findChunk(chunks, -1);
}
findChunk(chunks, delta) {
let i = this.index + delta;
let chunk = chunks[i];
while (chunk && chunk.size() === 0) {
i += delta;
chunk = chunks[i]
}
return chunk;
}
getTransitionBreakdown() {
return BreakDown(this.items, map => map.getType())
}
getUniqueTransitions() {
// Filter out all the maps that have parents within the same chunk.
return this.items.filter(map => !map.parent() || !this.has(map.parent()));
}
}
// ===========================================================================
function BreakDown(list, map_fn) {
if (map_fn === void 0) {
map_fn = each => each;
}
let breakdown = {__proto__:null};
list.forEach(each=> {
let type = map_fn(each);
let v = breakdown[type];
breakdown[type] = (v | 0) + 1
});
return Object.entries(breakdown)
.sort((a,b) => a[1] - b[1]);
}
// ===========================================================================
export class ArgumentsProcessor extends BaseArgumentsProcessor {
getArgsDispatch() {
return {
'--range': ['range', 'auto,auto',
'Specify the range limit as [start],[end]'
],
'--source-map': ['sourceMap', null,
'Specify the source map that should be used for output'
]
};
}
getDefaultResults() {
return {
logFileName: 'v8.log',
range: 'auto,auto',
};
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment