[tools] Harden and speed up map-processor
- avoid endless recursion with corrupted traces - speed up page by async bar repainting - minor tweaks to avoid unnecessary work - move helper functions to make command line version parse log files Change-Id: If8ce9cc4093030d648fbc7bbb60e53412e9f7a79 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2115434 Commit-Queue: Camillo Bruni <cbruni@chromium.org> Reviewed-by: Sathya Gunasekaran <gsathya@chromium.org> Cr-Commit-Position: refs/heads/master@{#66942}
This commit is contained in:
parent
a1bd722799
commit
91a60a4fcb
@ -46,14 +46,14 @@ class CsvParser {
|
||||
while (nextPos !== -1) {
|
||||
let escapeIdentifier = string.charAt(nextPos + 1);
|
||||
pos = nextPos + 2;
|
||||
if (escapeIdentifier == 'n') {
|
||||
if (escapeIdentifier === 'n') {
|
||||
result += '\n';
|
||||
nextPos = pos;
|
||||
} else if (escapeIdentifier == '\\') {
|
||||
} else if (escapeIdentifier === '\\') {
|
||||
result += '\\';
|
||||
nextPos = pos;
|
||||
} else {
|
||||
if (escapeIdentifier == 'x') {
|
||||
if (escapeIdentifier === 'x') {
|
||||
// \x00 ascii range escapes consume 2 chars.
|
||||
nextPos = pos + 2;
|
||||
} else {
|
||||
@ -71,7 +71,7 @@ class CsvParser {
|
||||
// If there are no more escape sequences consume the rest of the string.
|
||||
if (nextPos === -1) {
|
||||
result += string.substr(pos);
|
||||
} else if (pos != nextPos) {
|
||||
} else if (pos !== nextPos) {
|
||||
result += string.substring(pos, nextPos);
|
||||
}
|
||||
}
|
||||
|
@ -190,7 +190,6 @@ LogReader.prototype.dispatchLogRow_ = function(fields) {
|
||||
var command = fields[0];
|
||||
var dispatch = this.dispatchTable_[command];
|
||||
if (dispatch === undefined) return;
|
||||
|
||||
if (dispatch === null || this.skipDispatch(dispatch)) {
|
||||
return;
|
||||
}
|
||||
@ -241,7 +240,7 @@ LogReader.prototype.processLogLine_ = function(line) {
|
||||
var fields = this.csvParser_.parseLine(line);
|
||||
this.dispatchLogRow_(fields);
|
||||
} catch (e) {
|
||||
this.printError('line ' + (this.lineNum_ + 1) + ': ' + (e.message || e));
|
||||
this.printError('line ' + (this.lineNum_ + 1) + ': ' + (e.message || e) + '\n' + e.stack);
|
||||
}
|
||||
}
|
||||
this.lineNum_++;
|
||||
|
@ -452,19 +452,6 @@ function tr() {
|
||||
return document.createElement("tr");
|
||||
}
|
||||
|
||||
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, "histogram", function(mapFn) {
|
||||
let histogram = [];
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
@ -483,9 +470,6 @@ define(Array.prototype, "histogram", function(mapFn) {
|
||||
return histogram;
|
||||
});
|
||||
|
||||
define(Array.prototype, "first", function() { return this[0] });
|
||||
define(Array.prototype, "last", function() { return this[this.length - 1] });
|
||||
|
||||
// =========================================================================
|
||||
// EventHandlers
|
||||
function handleBodyLoad() {
|
||||
@ -698,6 +682,7 @@ class View {
|
||||
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);
|
||||
@ -711,10 +696,13 @@ class View {
|
||||
node.addEventListener("mousemove", e => this.handleChunkMouseMove(e));
|
||||
node.addEventListener("click", e => this.handleChunkClick(e));
|
||||
node.addEventListener("dblclick", e => this.handleChunkDoubleClick(e));
|
||||
this.setTimelineChunkBackground(chunk, node);
|
||||
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)));
|
||||
@ -753,6 +741,22 @@ class View {
|
||||
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;
|
||||
@ -779,7 +783,7 @@ class View {
|
||||
});
|
||||
}
|
||||
|
||||
let imageData = this.backgroundCanvas.toDataURL("image/png");
|
||||
let imageData = this.backgroundCanvas.toDataURL("image/webp", 0.2);
|
||||
node.style.backgroundImage = "url(" + imageData + ")";
|
||||
}
|
||||
|
||||
@ -818,7 +822,7 @@ class View {
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
let imageData = canvas.toDataURL("image/png");
|
||||
let imageData = canvas.toDataURL("image/webp", 0.2);
|
||||
$("timelineOverview").style.backgroundImage = "url(" + imageData + ")";
|
||||
}
|
||||
|
||||
@ -1122,11 +1126,16 @@ class StatsView {
|
||||
if (color !== null) {
|
||||
row.appendChild(td(div(['colorbox', color])));
|
||||
} else {
|
||||
row.appendChild(td(""));
|
||||
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.maps = this.timeline.filterUniqueTransitions(filter);
|
||||
row.onclick =
|
||||
(e) => this.transitionView.showMaps(e.target.parentNode.maps);
|
||||
row.appendChild(td(name));
|
||||
let count = this.timeline.count(filter);
|
||||
row.appendChild(td(count));
|
||||
|
@ -3,10 +3,28 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
// ===========================================================================
|
||||
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] });
|
||||
// ===========================================================================
|
||||
|
||||
class MapProcessor extends LogReader {
|
||||
constructor() {
|
||||
super();
|
||||
this.dispatchTable_ = {
|
||||
__proto__:null,
|
||||
'code-creation': {
|
||||
parsers: [parseString, parseInt, parseInt, parseInt, parseInt,
|
||||
parseString, parseVarArgs],
|
||||
@ -41,6 +59,7 @@ class MapProcessor extends LogReader {
|
||||
};
|
||||
this.profile_ = new Profile();
|
||||
this.timeline_ = new Timeline();
|
||||
this.formatPCRegexp_ = /(.*):[0-9]+:[0-9]+$/;
|
||||
}
|
||||
|
||||
printError(str) {
|
||||
@ -73,9 +92,15 @@ class MapProcessor extends LogReader {
|
||||
processLogFile(fileName) {
|
||||
this.collectEntries = true
|
||||
this.lastLogFileName_ = fileName;
|
||||
let i = 1;
|
||||
let line;
|
||||
while (line = readline()) {
|
||||
this.processLogLine(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();
|
||||
}
|
||||
@ -131,13 +156,12 @@ class MapProcessor extends LogReader {
|
||||
formatPC(pc, line, column) {
|
||||
let entry = this.profile_.findEntry(pc);
|
||||
if (!entry) return "<unknown>"
|
||||
if (entry.type == "Builtin") {
|
||||
if (entry.type === "Builtin") {
|
||||
return entry.name;
|
||||
}
|
||||
let name = entry.func.getName();
|
||||
let re = /(.*):[0-9]+:[0-9]+$/;
|
||||
let array = re.exec(name);
|
||||
if (!array) {
|
||||
let array = this.formatPCRegexp_.exec(name);
|
||||
if (array === null) {
|
||||
entry = name;
|
||||
} else {
|
||||
entry = entry.getState() + array[1];
|
||||
@ -146,12 +170,12 @@ class MapProcessor extends LogReader {
|
||||
}
|
||||
|
||||
processMap(type, time, from, to, pc, line, column, reason, name) {
|
||||
time = parseInt(time);
|
||||
if (type == "Deprecate") return this.deprecateMap(type, time, from);
|
||||
from = this.getExistingMap(from, time);
|
||||
to = this.getExistingMap(to, time);
|
||||
let edge = new Edge(type, name, reason, time, from, to);
|
||||
to.filePosition = this.formatPC(pc, line, column);
|
||||
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();
|
||||
}
|
||||
|
||||
@ -170,7 +194,7 @@ class MapProcessor extends LogReader {
|
||||
//TODO(cbruni): fix initial map logging.
|
||||
let map = this.getExistingMap(id, time);
|
||||
if (!map.description) {
|
||||
map.description = string;
|
||||
//map.description = string;
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,19 +236,30 @@ class V8Map {
|
||||
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
|
||||
this.children.forEach(edge => {
|
||||
let map = edge.to;
|
||||
currentId = map.finalize(currentId + 1);
|
||||
});
|
||||
this.rightId = currentId + 1;
|
||||
return currentId + 1;
|
||||
}
|
||||
|
||||
|
||||
parent() {
|
||||
if (this.edge === void 0) return void 0;
|
||||
return this.edge.from;
|
||||
@ -239,7 +274,7 @@ class V8Map {
|
||||
}
|
||||
|
||||
isRoot() {
|
||||
return this.edge == void 0 || this.edge.from == void 0;
|
||||
return this.edge === void 0 || this.edge.from === void 0;
|
||||
}
|
||||
|
||||
contains(map) {
|
||||
@ -300,16 +335,16 @@ class V8Map {
|
||||
}
|
||||
|
||||
static get(id) {
|
||||
if (!this.cache) return undefined;
|
||||
return this.cache.get(id);
|
||||
}
|
||||
|
||||
static set(id, map) {
|
||||
if (!this.cache) this.cache = new Map();
|
||||
this.cache.set(id, map);
|
||||
}
|
||||
}
|
||||
|
||||
V8Map.cache = new Map();
|
||||
|
||||
|
||||
// ===========================================================================
|
||||
class Edge {
|
||||
@ -323,21 +358,21 @@ class Edge {
|
||||
}
|
||||
|
||||
finishSetup() {
|
||||
if (this.from) this.from.addEdge(this);
|
||||
if (this.to) {
|
||||
this.to.edge = this;
|
||||
if (this.to === this.from) throw "From and to must be distinct.";
|
||||
if (this.from) {
|
||||
if (this.to.time < this.from.time) {
|
||||
console.error("invalid time order");
|
||||
}
|
||||
let newDepth = this.from.depth + 1;
|
||||
if (this.to.depth > 0 && this.to.depth != newDepth) {
|
||||
console.error("Depth has already been initialized");
|
||||
}
|
||||
this.to.depth = newDepth;
|
||||
}
|
||||
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) {
|
||||
@ -471,16 +506,16 @@ class Timeline {
|
||||
finalize() {
|
||||
let id = 0;
|
||||
this.forEach(map => {
|
||||
if (map.isRoot()) id = map.finalize(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);
|
||||
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);
|
||||
}
|
||||
@ -490,7 +525,7 @@ class Timeline {
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
return this.size() == 0
|
||||
return this.size() === 0
|
||||
}
|
||||
|
||||
size() {
|
||||
@ -573,7 +608,7 @@ class Timeline {
|
||||
|
||||
count(filter) {
|
||||
return this.values.reduce((sum, each) => {
|
||||
return sum + (filter(each) ? 1 : 0);
|
||||
return sum + (filter(each) === true ? 1 : 0);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
@ -584,10 +619,10 @@ class Timeline {
|
||||
filterUniqueTransitions(filter) {
|
||||
// Returns a list of Maps whose parent is not in the list.
|
||||
return this.values.filter(map => {
|
||||
if (!filter(map)) return false;
|
||||
if (filter(map) === false) return false;
|
||||
let parent = map.parent();
|
||||
if (!parent) return true;
|
||||
return !filter(parent);
|
||||
if (parent === undefined) return true;
|
||||
return filter(parent) === false;
|
||||
});
|
||||
}
|
||||
|
||||
@ -617,7 +652,7 @@ class Chunk {
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
return this.items.length == 0;
|
||||
return this.items.length === 0;
|
||||
}
|
||||
|
||||
last() {
|
||||
@ -662,7 +697,7 @@ class Chunk {
|
||||
findChunk(chunks, delta) {
|
||||
let i = this.index + delta;
|
||||
let chunk = chunks[i];
|
||||
while (chunk && chunk.size() == 0) {
|
||||
while (chunk && chunk.size() === 0) {
|
||||
i += delta;
|
||||
chunk = chunks[i]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user