blob: 49448bb9da0a197042c995525116be07dac2a497 [file] [log] [blame]
// Copyright 2020 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 {Profile} from '../profile.mjs';
import {DeoptLogEntry} from './log/deopt.mjs';
import {IcLogEntry} from './log/ic.mjs';
import {Edge, MapLogEntry} from './log/map.mjs';
import {Timeline} from './timeline.mjs';
// ===========================================================================
export class Processor extends LogReader {
_profile = new Profile();
_mapTimeline = new Timeline();
_icTimeline = new Timeline();
_deoptTimeline = new Timeline();
_formatPCRegexp = /(.*):[0-9]+:[0-9]+$/;
MAJOR_VERSION = 7;
MINOR_VERSION = 6;
constructor(logString) {
super();
this.propertyICParser = [
parseInt, parseInt, parseInt, parseInt, parseString, parseString,
parseString, parseString, parseString, parseString
];
this.dispatchTable_ = {
__proto__: null,
'code-creation': {
parsers: [
parseString, parseInt, parseInt, parseInt, parseInt, parseString,
parseVarArgs
],
processor: this.processCodeCreation
},
'code-deopt': {
parsers: [
parseInt, parseInt, parseInt, parseInt, parseInt, parseString,
parseString, parseString
],
processor: this.processCodeDeopt
},
'v8-version': {
parsers: [
parseInt,
parseInt,
],
processor: this.processV8Version
},
'script-source': {
parsers: [parseInt, parseString, parseString],
processor: this.processScriptSource
},
'code-move':
{parsers: [parseInt, parseInt], processor: this.processCodeMove},
'code-delete': {parsers: [parseInt], processor: this.processCodeDelete},
'sfi-move':
{parsers: [parseInt, parseInt], processor: this.processFunctionMove},
'map-create':
{parsers: [parseInt, parseString], processor: this.processMapCreate},
'map': {
parsers: [
parseString, parseInt, parseString, parseString, parseInt, parseInt,
parseInt, parseString, parseString
],
processor: this.processMap
},
'map-details': {
parsers: [parseInt, parseString, parseString],
processor: this.processMapDetails
},
'LoadGlobalIC': {
parsers: this.propertyICParser,
processor: this.processPropertyIC.bind(this, 'LoadGlobalIC')
},
'StoreGlobalIC': {
parsers: this.propertyICParser,
processor: this.processPropertyIC.bind(this, 'StoreGlobalIC')
},
'LoadIC': {
parsers: this.propertyICParser,
processor: this.processPropertyIC.bind(this, 'LoadIC')
},
'StoreIC': {
parsers: this.propertyICParser,
processor: this.processPropertyIC.bind(this, 'StoreIC')
},
'KeyedLoadIC': {
parsers: this.propertyICParser,
processor: this.processPropertyIC.bind(this, 'KeyedLoadIC')
},
'KeyedStoreIC': {
parsers: this.propertyICParser,
processor: this.processPropertyIC.bind(this, 'KeyedStoreIC')
},
'StoreInArrayLiteralIC': {
parsers: this.propertyICParser,
processor: this.processPropertyIC.bind(this, 'StoreInArrayLiteralIC')
},
};
if (logString) this.processString(logString);
}
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}`);
}
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);
}
this.finalize();
}
finalize() {
// TODO(cbruni): print stats;
this._mapTimeline.transitions = new Map();
let id = 0;
this._mapTimeline.forEach(map => {
if (map.isRoot()) id = map.finalizeRootMap(id + 1);
if (map.edge && map.edge.name) {
const edge = map.edge;
const list = this._mapTimeline.transitions.get(edge.name);
if (list === undefined) {
this._mapTimeline.transitions.set(edge.name, [edge]);
} else {
list.push(edge);
}
}
});
}
/**
* 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) {
const funcAddr = parseInt(maybe_func[0]);
const 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);
}
}
processCodeDeopt(
timestamp, codeSize, instructionStart, inliningId, scriptOffset,
deoptKind, deoptLocation, deoptReason) {
this._deoptTimeline.push(new DeoptLogEntry(deoptKind, timestamp));
}
processV8Version(majorVersion, minorVersion) {
if ((majorVersion == this.MAJOR_VERSION &&
minorVersion <= this.MINOR_VERSION) ||
(majorVersion < this.MAJOR_VERSION)) {
window.alert(
`Unsupported version ${majorVersion}.${minorVersion}. \n` +
`Please use the matching tool for given the V8 version.`);
}
}
processScriptSource(scriptId, url, source) {
this._profile.addScriptSource(scriptId, url, source);
}
processCodeMove(from, to) {
this._profile.moveCode(from, to);
}
processCodeDelete(start) {
this._profile.deleteCode(start);
}
processFunctionMove(from, to) {
this._profile.moveFunc(from, to);
}
formatName(entry) {
if (!entry) return '<unknown>';
let name = entry.func.getName();
let re = /(.*):[0-9]+:[0-9]+$/;
let array = re.exec(name);
if (!array) return name;
return entry.getState() + array[1];
}
processPropertyIC(
type, pc, time, line, column, old_state, new_state, map, key, modifier,
slow_reason) {
let fnName = this.functionName(pc);
let parts = fnName.split(' ');
let fileName = parts[parts.length - 1];
let script = this.getScript(fileName);
// TODO: Use SourcePosition here directly
let entry = new IcLogEntry(
type, fnName, time, line, column, key, old_state, new_state, map,
slow_reason, script, modifier);
if (script) {
entry.sourcePosition = script.addSourcePosition(line, column, entry);
}
this._icTimeline.push(entry);
}
functionName(pc) {
let entry = this._profile.findEntry(pc);
return this.formatName(entry);
}
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;
}
processFileName(filePositionLine) {
if (!filePositionLine.includes(' ')) return;
// Try to handle urls with file positions: https://foo.bar.com/:17:330"
filePositionLine = filePositionLine.split(' ');
let parts = filePositionLine[1].split(':');
if (parts[0].length <= 5) return parts[0] + ':' + parts[1];
return parts[1];
}
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.getExistingMapEntry(from, time_);
let to_ = this.getExistingMapEntry(to, time_);
// TODO: use SourcePosition directly.
let edge = new Edge(type, name, reason, time, from_, to_);
to_.filePosition = this.formatPC(pc, line, column);
let fileName = this.processFileName(to_.filePosition);
// TODO: avoid undefined source positions.
if (fileName !== undefined) {
to_.script = this.getScript(fileName);
}
if (to_.script) {
to_.sourcePosition = to_.script.addSourcePosition(line, column, to_)
}
edge.finishSetup();
}
deprecateMap(type, time, id) {
this.getExistingMapEntry(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.createMapEntry(id, time);
}
processMapDetails(time, id, string) {
// TODO(cbruni): fix initial map logging.
let map = this.getExistingMapEntry(id, time);
map.description = string;
}
createMapEntry(id, time) {
let map = new MapLogEntry(id, time);
this._mapTimeline.push(map);
return map;
}
getExistingMapEntry(id, time) {
if (id === '0x000000000000') return undefined;
let map = MapLogEntry.get(id, time);
if (map === undefined) {
console.error(`No map details provided: id=${id}`);
// Manually patch in a map to continue running.
return this.createMapEntry(id, time);
};
return map;
}
getScript(url) {
const script = this._profile.getScript(url);
// TODO create placeholder script for empty urls.
if (script === undefined) {
console.error(`Could not find script for url: '${url}'`)
}
return script;
}
get icTimeline() {
return this._icTimeline;
}
get mapTimeline() {
return this._mapTimeline;
}
get deoptTimeline() {
return this._deoptTimeline;
}
get scripts() {
return this._profile.scripts_.filter(script => script !== undefined);
}
}